From 87f66515541e89162363e60254652b725e296ec9 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Thu, 27 Nov 2014 17:10:35 -0300 Subject: [PATCH 01/20] initial Script refactor --- lib/script.js | 159 ++++++++++++++++++++++---------------------------- 1 file changed, 71 insertions(+), 88 deletions(-) diff --git a/lib/script.js b/lib/script.js index a33a999..d884aee 100644 --- a/lib/script.js +++ b/lib/script.js @@ -4,22 +4,19 @@ var BufferReader = require('./encoding/bufferreader'); var BufferWriter = require('./encoding/bufferwriter'); var Opcode = require('./opcode'); -var Script = function Script(buf) { - if (!(this instanceof Script)) - return new Script(buf); - +var Script = function Script(from) { + if (!(this instanceof Script)) { + return new Script(from); + } + this.chunks = []; - if (Buffer.isBuffer(buf)) { - this.fromBuffer(buf); - } - else if (typeof buf === 'string') { - var str = buf; - this.fromString(str); - } - else if (typeof buf !== 'undefined') { - var obj = buf; - this.set(obj); + if (Buffer.isBuffer(from)) { + this.fromBuffer(from); + } else if (typeof from === 'string') { + this.fromString(from); + } else if (typeof from !== 'undefined') { + this.set(from); } }; @@ -28,33 +25,26 @@ Script.prototype.set = function(obj) { return this; }; -Script.prototype.fromJSON = function(json) { - return this.fromString(json); -}; - -Script.prototype.toJSON = function() { - return this.toString(); -}; +Script.fromBuffer = function(buffer) { + var script = new Script(); + script.chunks = []; -Script.prototype.fromBuffer = function(buf) { - this.chunks = []; - - var br = new BufferReader(buf); + var br = new BufferReader(buffer); while (!br.eof()) { var opcodenum = br.readUInt8(); var len, buf; if (opcodenum > 0 && opcodenum < Opcode.map.OP_PUSHDATA1) { len = opcodenum; - this.chunks.push({ + script.chunks.push({ buf: br.read(len), len: len, opcodenum: opcodenum }); } else if (opcodenum === Opcode.map.OP_PUSHDATA1) { len = br.readUInt8(); - var buf = br.read(len); - this.chunks.push({ + buf = br.read(len); + script.chunks.push({ buf: buf, len: len, opcodenum: opcodenum @@ -62,7 +52,7 @@ Script.prototype.fromBuffer = function(buf) { } else if (opcodenum === Opcode.map.OP_PUSHDATA2) { len = br.readUInt16LE(); buf = br.read(len); - this.chunks.push({ + script.chunks.push({ buf: buf, len: len, opcodenum: opcodenum @@ -70,17 +60,17 @@ Script.prototype.fromBuffer = function(buf) { } else if (opcodenum === Opcode.map.OP_PUSHDATA4) { len = br.readUInt32LE(); buf = br.read(len); - this.chunks.push({ + script.chunks.push({ buf: buf, len: len, opcodenum: opcodenum }); } else { - this.chunks.push(opcodenum); + script.chunks.push(opcodenum); } } - return this; + return script; }; Script.prototype.toBuffer = function() { @@ -88,24 +78,22 @@ Script.prototype.toBuffer = function() { for (var i = 0; i < this.chunks.length; i++) { var chunk = this.chunks[i]; + var opcodenum; if (typeof chunk === 'number') { - var opcodenum = chunk; + opcodenum = chunk; bw.writeUInt8(opcodenum); } else { - var opcodenum = chunk.opcodenum; + opcodenum = chunk.opcodenum; bw.writeUInt8(chunk.opcodenum); if (opcodenum < Opcode.map.OP_PUSHDATA1) { bw.write(chunk.buf); - } - else if (opcodenum === Opcode.map.OP_PUSHDATA1) { + } else if (opcodenum === Opcode.map.OP_PUSHDATA1) { bw.writeUInt8(chunk.len); bw.write(chunk.buf); - } - else if (opcodenum === Opcode.map.OP_PUSHDATA2) { + } else if (opcodenum === Opcode.map.OP_PUSHDATA2) { bw.writeUInt16LE(chunk.len); bw.write(chunk.buf); - } - else if (opcodenum === Opcode.map.OP_PUSHDATA4) { + } else if (opcodenum === Opcode.map.OP_PUSHDATA4) { bw.writeUInt32LE(chunk.len); bw.write(chunk.buf); } @@ -134,13 +122,13 @@ Script.prototype.fromString = function(str) { opcodenum: opcodenum }); i = i + 2; - } - else { + } else { throw new Error('Invalid script'); } } else if (opcodenum === Opcode.map.OP_PUSHDATA1 || opcodenum === Opcode.map.OP_PUSHDATA2 || opcodenum === Opcode.map.OP_PUSHDATA4) { - if (tokens[i + 2].slice(0, 2) != '0x') + if (tokens[i + 2].slice(0, 2) !== '0x') { throw new Error('Pushdata data must start with 0x'); + } this.chunks.push({ buf: new Buffer(tokens[i + 2].slice(2), 'hex'), len: parseInt(tokens[i + 1]), @@ -156,19 +144,23 @@ Script.prototype.fromString = function(str) { }; Script.prototype.toString = function() { - var str = ""; + var str = ''; for (var i = 0; i < this.chunks.length; i++) { var chunk = this.chunks[i]; + var opcodenum; if (typeof chunk === 'number') { - var opcodenum = chunk; - str = str + Opcode(opcodenum).toString() + " "; + opcodenum = chunk; + str = str + Opcode(opcodenum).toString() + ' '; } else { - var opcodenum = chunk.opcodenum; - if (opcodenum === Opcode.map.OP_PUSHDATA1 || opcodenum === Opcode.map.OP_PUSHDATA2 || opcodenum === Opcode.map.OP_PUSHDATA4) - str = str + Opcode(opcodenum).toString() + " " ; - str = str + chunk.len + " " ; - str = str + "0x" + chunk.buf.toString('hex') + " "; + opcodenum = chunk.opcodenum; + if (opcodenum === Opcode.map.OP_PUSHDATA1 || + opcodenum === Opcode.map.OP_PUSHDATA2 || + opcodenum === Opcode.map.OP_PUSHDATA4) { + str = str + Opcode(opcodenum).toString() + ' '; + } + str = str + chunk.len + ' '; + str = str + '0x' + chunk.buf.toString('hex') + ' '; } } @@ -176,14 +168,9 @@ Script.prototype.toString = function() { }; Script.prototype.isOpReturn = function() { - if (this.chunks[0] === Opcode('OP_RETURN').toNumber() - && - (this.chunks.length === 1 - || - (this.chunks.length === 2 - && this.chunks[1].buf - && this.chunks[1].buf.length <= 40 - && this.chunks[1].length === this.chunks.len))) { + if (this.chunks[0] === Opcode('OP_RETURN').toNumber() && + (this.chunks.length === 1 || + (this.chunks.length === 2 && this.chunks[1].buf && this.chunks[1].buf.length <= 40 && this.chunks[1].length === this.chunks.len))) { return true; } else { return false; @@ -191,11 +178,7 @@ Script.prototype.isOpReturn = function() { }; Script.prototype.isPublicKeyHashOut = function() { - if (this.chunks[0] === Opcode('OP_DUP').toNumber() - && this.chunks[1] === Opcode('OP_HASH160').toNumber() - && this.chunks[2].buf - && this.chunks[3] === Opcode('OP_EQUALVERIFY').toNumber() - && this.chunks[4] === Opcode('OP_CHECKSIG').toNumber()) { + if (this.chunks[0] === Opcode('OP_DUP').toNumber() && this.chunks[1] === Opcode('OP_HASH160').toNumber() && this.chunks[2].buf && this.chunks[3] === Opcode('OP_EQUALVERIFY').toNumber() && this.chunks[4] === Opcode('OP_CHECKSIG').toNumber()) { return true; } else { return false; @@ -203,9 +186,7 @@ Script.prototype.isPublicKeyHashOut = function() { }; Script.prototype.isPublicKeyHashIn = function() { - if (this.chunks.length === 2 - && this.chunks[0].buf - && this.chunks[1].buf) { + if (this.chunks.length === 2 && this.chunks[0].buf && this.chunks[1].buf) { return true; } else { return false; @@ -213,11 +194,7 @@ Script.prototype.isPublicKeyHashIn = function() { }; Script.prototype.isScriptHashOut = function() { - if (this.chunks.length === 3 - && this.chunks[0] === Opcode('OP_HASH160').toNumber() - && this.chunks[1].buf - && this.chunks[1].buf.length === 20 - && this.chunks[2] === Opcode('OP_EQUAL').toNumber()) { + if (this.chunks.length === 3 && this.chunks[0] === Opcode('OP_HASH160').toNumber() && this.chunks[1].buf && this.chunks[1].buf.length === 20 && this.chunks[2] === Opcode('OP_EQUAL').toNumber()) { return true; } else { return false; @@ -236,29 +213,35 @@ Script.prototype.isScriptHashIn = function() { } }; -Script.prototype.write = function(obj) { - if (typeof obj === 'string') - this.writeOp(obj); - else if (typeof obj === 'number') - this.writeOp(obj); - else if (Buffer.isBuffer(obj)) - this.writeBuffer(obj); - else if (typeof obj === 'object') +Script.prototype.add = function(obj) { + if (typeof obj === 'string') { + this._addOpcode(obj); + } else if (typeof obj === 'number') { + this._addOpcode(obj); + } else if (obj.constructor && obj.constructor.name && obj.constructor.name === 'Opcode') { + this._addOpcode(obj); + } else if (Buffer.isBuffer(obj)) { + this._addBuffer(obj); + } else if (typeof obj === 'object') { this.chunks.push(obj); - else + } else { throw new Error('Invalid script chunk'); + } return this; }; -Script.prototype.writeOp = function(str) { - if (typeof str === 'number') - this.chunks.push(str); - else - this.chunks.push(Opcode(str).toNumber()); +Script.prototype._addOpcode = function(opcode) { + if (typeof opcode === 'number') { + this.chunks.push(opcode); + } else if (opcode.constructor && opcode.constructor.name && opcode.constructor.name === 'Opcode') { + this.chunks.push(opcode.toNumber()); + } else { + this.chunks.push(Opcode(opcode).toNumber()); + } return this; }; -Script.prototype.writeBuffer = function(buf) { +Script.prototype._addBuffer = function(buf) { var opcodenum; var len = buf.length; if (buf.length > 0 && buf.length < Opcode.map.OP_PUSHDATA1) { @@ -270,7 +253,7 @@ Script.prototype.writeBuffer = function(buf) { } else if (buf.length < Math.pow(2, 32)) { opcodenum = Opcode.map.OP_PUSHDATA4; } else { - throw new Error("You can't push that much data"); + throw new Error('You can\'t push that much data'); } this.chunks.push({ buf: buf, From 66e96e5fa4ad5ae58e35cd8777e4e0ce2f2c6fdb Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Thu, 27 Nov 2014 18:30:16 -0300 Subject: [PATCH 02/20] make tests pass with Script refactor --- lib/script.js | 17 +++---- lib/txin.js | 6 +-- lib/txout.js | 6 +-- test/address.js | 4 +- test/script.js | 121 ++++++++++++++++++++---------------------------- test/txin.js | 2 +- test/txout.js | 6 +-- 7 files changed, 70 insertions(+), 92 deletions(-) diff --git a/lib/script.js b/lib/script.js index d884aee..5b875e6 100644 --- a/lib/script.js +++ b/lib/script.js @@ -12,9 +12,9 @@ var Script = function Script(from) { this.chunks = []; if (Buffer.isBuffer(from)) { - this.fromBuffer(from); + return Script.fromBuffer(from); } else if (typeof from === 'string') { - this.fromString(from); + return Script.fromString(from); } else if (typeof from !== 'undefined') { this.set(from); } @@ -103,8 +103,9 @@ Script.prototype.toBuffer = function() { return bw.concat(); }; -Script.prototype.fromString = function(str) { - this.chunks = []; +Script.fromString = function(str) { + var script = new Script(); + script.chunks = []; var tokens = str.split(' '); var i = 0; @@ -116,7 +117,7 @@ Script.prototype.fromString = function(str) { if (typeof opcodenum === 'undefined') { opcodenum = parseInt(token); if (opcodenum > 0 && opcodenum < Opcode.map.OP_PUSHDATA1) { - this.chunks.push({ + script.chunks.push({ buf: new Buffer(tokens[i + 1].slice(2), 'hex'), len: opcodenum, opcodenum: opcodenum @@ -129,18 +130,18 @@ Script.prototype.fromString = function(str) { if (tokens[i + 2].slice(0, 2) !== '0x') { throw new Error('Pushdata data must start with 0x'); } - this.chunks.push({ + script.chunks.push({ buf: new Buffer(tokens[i + 2].slice(2), 'hex'), len: parseInt(tokens[i + 1]), opcodenum: opcodenum }); i = i + 3; } else { - this.chunks.push(opcodenum); + script.chunks.push(opcodenum); i = i + 1; } } - return this; + return script; }; Script.prototype.toString = function() { diff --git a/lib/txin.js b/lib/txin.js index 75d2a05..dbe62e1 100644 --- a/lib/txin.js +++ b/lib/txin.js @@ -36,7 +36,7 @@ Txin.prototype.fromJSON = function(json) { txidbuf: new Buffer(json.txidbuf, 'hex'), txoutnum: json.txoutnum, scriptvi: Varint().fromJSON(json.scriptvi), - script: Script().fromJSON(json.script), + script: Script.fromString(json.script), seqnum: json.seqnum }); return this; @@ -47,7 +47,7 @@ Txin.prototype.toJSON = function() { txidbuf: this.txidbuf.toString('hex'), txoutnum: this.txoutnum, scriptvi: this.scriptvi.toJSON(), - script: this.script.toJSON(), + script: this.script.toString(), seqnum: this.seqnum }; }; @@ -60,7 +60,7 @@ Txin.prototype.fromBufferReader = function(br) { this.txidbuf = br.read(32); this.txoutnum = br.readUInt32LE(); this.scriptvi = Varint(br.readVarintBuf()); - this.script = Script().fromBuffer(br.read(this.scriptvi.toNumber())); + this.script = Script.fromBuffer(br.read(this.scriptvi.toNumber())); this.seqnum = br.readUInt32LE(); return this; }; diff --git a/lib/txout.js b/lib/txout.js index 22a688d..1f40b77 100644 --- a/lib/txout.js +++ b/lib/txout.js @@ -32,7 +32,7 @@ Txout.prototype.fromJSON = function(json) { this.set({ valuebn: BN().fromJSON(json.valuebn), scriptvi: Varint().fromJSON(json.scriptvi), - script: Script().fromJSON(json.script) + script: Script.fromString(json.script) }); return this; }; @@ -41,7 +41,7 @@ Txout.prototype.toJSON = function() { return { valuebn: this.valuebn.toJSON(), scriptvi: this.scriptvi.toJSON(), - script: this.script.toJSON() + script: this.script.toString() }; }; @@ -52,7 +52,7 @@ Txout.prototype.fromBuffer = function(buf) { Txout.prototype.fromBufferReader = function(br) { this.valuebn = br.readUInt64LEBN(); this.scriptvi = Varint(br.readVarintNum()); - this.script = Script().fromBuffer(br.read(this.scriptvi.toNumber())); + this.script = Script.fromBuffer(br.read(this.scriptvi.toNumber())); return this; }; diff --git a/test/address.js b/test/address.js index 3c29c3b..ce798eb 100644 --- a/test/address.js +++ b/test/address.js @@ -294,7 +294,7 @@ describe('Address', function() { }); it('should make this address from a script', function() { - var s = Script().fromString("OP_CHECKMULTISIG"); + var s = Script.fromString("OP_CHECKMULTISIG"); var buf = s.toBuffer(); var a = Address.fromScript(s); a.toString().should.equal('3BYmEwgV2vANrmfRymr1mFnHXgLjD6gAWm'); @@ -305,7 +305,7 @@ describe('Address', function() { }); it('should make this address from other script', function() { - var s = Script().fromString("OP_CHECKSIG OP_HASH160"); + var s = Script.fromString("OP_CHECKSIG OP_HASH160"); var a = Address.fromScript(s); a.toString().should.equal('347iRqVwks5r493N1rsLN4k9J7Ljg488W7'); var b = new Address(s); diff --git a/test/script.js b/test/script.js index 721586f..94fccbd 100644 --- a/test/script.js +++ b/test/script.js @@ -6,18 +6,18 @@ var Script = bitcore.Script; var Opcode = bitcore.Opcode; describe('Script', function() { - + it('should make a new script', function() { var script = new Script(); should.exist(script); }); describe('#fromBuffer', function() { - + it('should parse this buffer containing an OP code', function() { var buf = new Buffer(1); buf[0] = Opcode('OP_0').toNumber(); - var script = Script().fromBuffer(buf); + var script = Script.fromBuffer(buf); script.chunks.length.should.equal(1); script.chunks[0].should.equal(buf[0]); }); @@ -25,14 +25,14 @@ describe('Script', function() { it('should parse this buffer containing another OP code', function() { var buf = new Buffer(1); buf[0] = Opcode('OP_CHECKMULTISIG').toNumber(); - var script = Script().fromBuffer(buf); + var script = Script.fromBuffer(buf); script.chunks.length.should.equal(1); script.chunks[0].should.equal(buf[0]); }); it('should parse this buffer containing three bytes of data', function() { var buf = new Buffer([3, 1, 2, 3]); - var script = Script().fromBuffer(buf); + var script = Script.fromBuffer(buf); script.chunks.length.should.equal(1); script.chunks[0].buf.toString('hex').should.equal('010203'); }); @@ -41,7 +41,7 @@ describe('Script', function() { var buf = new Buffer([0, 0, 1, 2, 3]); buf[0] = Opcode('OP_PUSHDATA1').toNumber(); buf.writeUInt8(3, 1); - var script = Script().fromBuffer(buf); + var script = Script.fromBuffer(buf); script.chunks.length.should.equal(1); script.chunks[0].buf.toString('hex').should.equal('010203'); }); @@ -50,7 +50,7 @@ describe('Script', function() { var buf = new Buffer([0, 0, 0, 1, 2, 3]); buf[0] = Opcode('OP_PUSHDATA2').toNumber(); buf.writeUInt16LE(3, 1); - var script = Script().fromBuffer(buf); + var script = Script.fromBuffer(buf); script.chunks.length.should.equal(1); script.chunks[0].buf.toString('hex').should.equal('010203'); }); @@ -59,7 +59,7 @@ describe('Script', function() { var buf = new Buffer([0, 0, 0, 0, 0, 1, 2, 3]); buf[0] = Opcode('OP_PUSHDATA4').toNumber(); buf.writeUInt16LE(3, 1); - var script = Script().fromBuffer(buf); + var script = Script.fromBuffer(buf); script.chunks.length.should.equal(1); script.chunks[0].buf.toString('hex').should.equal('010203'); }); @@ -70,7 +70,7 @@ describe('Script', function() { buf[1] = Opcode('OP_PUSHDATA4').toNumber(); buf.writeUInt16LE(3, 2); buf[buf.length - 1] = Opcode('OP_0').toNumber(); - var script = Script().fromBuffer(buf); + var script = Script.fromBuffer(buf); script.chunks.length.should.equal(3); script.chunks[0].should.equal(buf[0]); script.chunks[1].buf.toString('hex').should.equal('010203'); @@ -80,11 +80,11 @@ describe('Script', function() { }); describe('#toBuffer', function() { - + it('should output this buffer containing an OP code', function() { var buf = new Buffer(1); buf[0] = Opcode('OP_0').toNumber(); - var script = Script().fromBuffer(buf); + var script = Script.fromBuffer(buf); script.chunks.length.should.equal(1); script.chunks[0].should.equal(buf[0]); script.toBuffer().toString('hex').should.equal(buf.toString('hex')); @@ -93,7 +93,7 @@ describe('Script', function() { it('should output this buffer containing another OP code', function() { var buf = new Buffer(1); buf[0] = Opcode('OP_CHECKMULTISIG').toNumber(); - var script = Script().fromBuffer(buf); + var script = Script.fromBuffer(buf); script.chunks.length.should.equal(1); script.chunks[0].should.equal(buf[0]); script.toBuffer().toString('hex').should.equal(buf.toString('hex')); @@ -101,7 +101,7 @@ describe('Script', function() { it('should output this buffer containing three bytes of data', function() { var buf = new Buffer([3, 1, 2, 3]); - var script = Script().fromBuffer(buf); + var script = Script.fromBuffer(buf); script.chunks.length.should.equal(1); script.chunks[0].buf.toString('hex').should.equal('010203'); script.toBuffer().toString('hex').should.equal(buf.toString('hex')); @@ -111,7 +111,7 @@ describe('Script', function() { var buf = new Buffer([0, 0, 1, 2, 3]); buf[0] = Opcode('OP_PUSHDATA1').toNumber(); buf.writeUInt8(3, 1); - var script = Script().fromBuffer(buf); + var script = Script.fromBuffer(buf); script.chunks.length.should.equal(1); script.chunks[0].buf.toString('hex').should.equal('010203'); script.toBuffer().toString('hex').should.equal(buf.toString('hex')); @@ -121,7 +121,7 @@ describe('Script', function() { var buf = new Buffer([0, 0, 0, 1, 2, 3]); buf[0] = Opcode('OP_PUSHDATA2').toNumber(); buf.writeUInt16LE(3, 1); - var script = Script().fromBuffer(buf); + var script = Script.fromBuffer(buf); script.chunks.length.should.equal(1); script.chunks[0].buf.toString('hex').should.equal('010203'); script.toBuffer().toString('hex').should.equal(buf.toString('hex')); @@ -131,7 +131,7 @@ describe('Script', function() { var buf = new Buffer([0, 0, 0, 0, 0, 1, 2, 3]); buf[0] = Opcode('OP_PUSHDATA4').toNumber(); buf.writeUInt16LE(3, 1); - var script = Script().fromBuffer(buf); + var script = Script.fromBuffer(buf); script.chunks.length.should.equal(1); script.chunks[0].buf.toString('hex').should.equal('010203'); script.toBuffer().toString('hex').should.equal(buf.toString('hex')); @@ -143,7 +143,7 @@ describe('Script', function() { buf[1] = Opcode('OP_PUSHDATA4').toNumber(); buf.writeUInt16LE(3, 2); buf[buf.length - 1] = Opcode('OP_0').toNumber(); - var script = Script().fromBuffer(buf); + var script = Script.fromBuffer(buf); script.chunks.length.should.equal(3); script.chunks[0].should.equal(buf[0]); script.chunks[1].buf.toString('hex').should.equal('010203'); @@ -156,10 +156,10 @@ describe('Script', function() { describe('#fromString', function() { it('should parse these known scripts', function() { - Script().fromString('OP_0 OP_PUSHDATA4 3 0x010203 OP_0').toString().should.equal('OP_0 OP_PUSHDATA4 3 0x010203 OP_0'); - Script().fromString('OP_0 OP_PUSHDATA2 3 0x010203 OP_0').toString().should.equal('OP_0 OP_PUSHDATA2 3 0x010203 OP_0'); - Script().fromString('OP_0 OP_PUSHDATA1 3 0x010203 OP_0').toString().should.equal('OP_0 OP_PUSHDATA1 3 0x010203 OP_0'); - Script().fromString('OP_0 3 0x010203 OP_0').toString().should.equal('OP_0 3 0x010203 OP_0'); + Script.fromString('OP_0 OP_PUSHDATA4 3 0x010203 OP_0').toString().should.equal('OP_0 OP_PUSHDATA4 3 0x010203 OP_0'); + Script.fromString('OP_0 OP_PUSHDATA2 3 0x010203 OP_0').toString().should.equal('OP_0 OP_PUSHDATA2 3 0x010203 OP_0'); + Script.fromString('OP_0 OP_PUSHDATA1 3 0x010203 OP_0').toString().should.equal('OP_0 OP_PUSHDATA1 3 0x010203 OP_0'); + Script.fromString('OP_0 3 0x010203 OP_0').toString().should.equal('OP_0 3 0x010203 OP_0'); }); }); @@ -172,7 +172,7 @@ describe('Script', function() { buf[1] = Opcode('OP_PUSHDATA4').toNumber(); buf.writeUInt16LE(3, 2); buf[buf.length - 1] = Opcode('OP_0').toNumber(); - var script = Script().fromBuffer(buf); + var script = Script.fromBuffer(buf); script.chunks.length.should.equal(3); script.chunks[0].should.equal(buf[0]); script.chunks[1].buf.toString('hex').should.equal('010203'); @@ -182,24 +182,8 @@ describe('Script', function() { }); - describe('#fromJSON', function() { - - it('should parse this known script', function() { - Script().fromJSON('OP_0 OP_PUSHDATA4 3 0x010203 OP_0').toString().should.equal('OP_0 OP_PUSHDATA4 3 0x010203 OP_0'); - }); - - }); - - describe('#toJSON', function() { - - it('should output this known script', function() { - Script().fromString('OP_0 OP_PUSHDATA4 3 0x010203 OP_0').toJSON().should.equal('OP_0 OP_PUSHDATA4 3 0x010203 OP_0'); - }); - - }); - describe('#isOpReturn', function() { - + it('should know this is a (blank) OP_RETURN script', function() { Script('OP_RETURN').isOpReturn().should.equal(true); }); @@ -219,7 +203,7 @@ describe('Script', function() { }); describe('#isPublicKeyHashIn', function() { - + it('should classify this known pubkeyhashin', function() { Script('73 0x3046022100bb3c194a30e460d81d34be0a230179c043a656f67e3c5c8bf47eceae7c4042ee0221008bf54ca11b2985285be0fd7a212873d243e6e73f5fad57e8eb14c4f39728b8c601 65 0x04e365859b3c78a8b7c202412b949ebca58e147dba297be29eee53cd3e1d300a6419bc780cc9aec0dc94ed194e91c8f6433f1b781ee00eac0ead2aae1e8e0712c6').isPublicKeyHashIn().should.equal(true); }); @@ -243,7 +227,7 @@ describe('Script', function() { }); describe('#isScripthashIn', function() { - + it('should classify this known scripthashin', function() { Script('20 0000000000000000000000000000000000000000').isScriptHashIn().should.equal(true); }); @@ -267,44 +251,37 @@ describe('Script', function() { }); - describe('#writeOp', function() { + describe('#add', function() { - it('should write these ops', function() { - Script().writeOp('OP_CHECKMULTISIG').toString().should.equal('OP_CHECKMULTISIG'); - Script().writeOp(Opcode.map.OP_CHECKMULTISIG).toString().should.equal('OP_CHECKMULTISIG'); + it('should add these ops', function() { + Script().add('OP_CHECKMULTISIG').toString().should.equal('OP_CHECKMULTISIG'); + Script().add(Opcode.map.OP_CHECKMULTISIG).toString().should.equal('OP_CHECKMULTISIG'); }); }); - describe('#writeBuffer', function() { - - it('should write these push data', function() { - var buf = new Buffer(1); - buf.fill(0); - Script().writeBuffer(buf).toString().should.equal('1 0x00'); - buf = new Buffer(255); - buf.fill(0); - Script().writeBuffer(buf).toString().should.equal('OP_PUSHDATA1 255 0x' + buf.toString('hex')); - buf = new Buffer(256); - buf.fill(0); - Script().writeBuffer(buf).toString().should.equal('OP_PUSHDATA2 256 0x' + buf.toString('hex')); - buf = new Buffer(Math.pow(2, 16)); - buf.fill(0); - Script().writeBuffer(buf).toString().should.equal('OP_PUSHDATA4 ' + Math.pow(2, 16) + ' 0x' + buf.toString('hex')); - }); - + it('should add these push data', function() { + var buf = new Buffer(1); + buf.fill(0); + Script().add(buf).toString().should.equal('1 0x00'); + buf = new Buffer(255); + buf.fill(0); + Script().add(buf).toString().should.equal('OP_PUSHDATA1 255 0x' + buf.toString('hex')); + buf = new Buffer(256); + buf.fill(0); + Script().add(buf).toString().should.equal('OP_PUSHDATA2 256 0x' + buf.toString('hex')); + buf = new Buffer(Math.pow(2, 16)); + buf.fill(0); + Script().add(buf).toString().should.equal('OP_PUSHDATA4 ' + Math.pow(2, 16) + ' 0x' + buf.toString('hex')); }); - describe('#write', function() { - - it('should write both pushdata and non-pushdata chunks', function() { - Script().write('OP_CHECKMULTISIG').toString().should.equal('OP_CHECKMULTISIG'); - Script().write(Opcode.map.OP_CHECKMULTISIG).toString().should.equal('OP_CHECKMULTISIG'); - var buf = new Buffer(1); - buf.fill(0); - Script().write(buf).toString().should.equal('1 0x00'); - }); - + it('should add both pushdata and non-pushdata chunks', function() { + Script().add('OP_CHECKMULTISIG').toString().should.equal('OP_CHECKMULTISIG'); + Script().add(Opcode.map.OP_CHECKMULTISIG).toString().should.equal('OP_CHECKMULTISIG'); + var buf = new Buffer(1); + buf.fill(0); + Script().add(buf).toString().should.equal('1 0x00'); }); + }); diff --git a/test/txin.js b/test/txin.js index 5147df8..88dae01 100644 --- a/test/txin.js +++ b/test/txin.js @@ -12,7 +12,7 @@ describe('Txin', function() { var txidbuf = new Buffer(32); txidbuf.fill(0); var txoutnum = 0; - var script = Script().fromString('OP_CHECKMULTISIG'); + var script = Script.fromString('OP_CHECKMULTISIG'); var scriptvi = Varint(script.toBuffer().length); var seqnum = 0; var txin = Txin().set({ diff --git a/test/txout.js b/test/txout.js index 0d1f13b..8d13cba 100644 --- a/test/txout.js +++ b/test/txout.js @@ -11,7 +11,7 @@ var Script = bitcore.Script; describe('Txout', function() { var valuebn = BN(5); - var script = Script().fromString('OP_CHECKMULTISIG'); + var script = Script.fromString('OP_CHECKMULTISIG'); var scriptvi = Varint(script.toBuffer().length); it('should make a new txout', function() { @@ -43,7 +43,7 @@ describe('Txout', function() { var txout = Txout().fromJSON({ valuebn: valuebn.toJSON(), scriptvi: scriptvi.toJSON(), - script: script.toJSON() + script: script.toString() }); should.exist(txout.valuebn); should.exist(txout.scriptvi); @@ -58,7 +58,7 @@ describe('Txout', function() { var txout = Txout().fromJSON({ valuebn: valuebn.toJSON(), scriptvi: scriptvi.toJSON(), - script: script.toJSON() + script: script.toString() }); var json = txout.toJSON(); should.exist(json.valuebn); From 98be01b2071e20b6ae924f912a58cdb05e0f2c3a Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Fri, 28 Nov 2014 11:08:33 -0300 Subject: [PATCH 03/20] fix conditional format --- lib/script.js | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/lib/script.js b/lib/script.js index 5b875e6..554a801 100644 --- a/lib/script.js +++ b/lib/script.js @@ -171,7 +171,10 @@ Script.prototype.toString = function() { Script.prototype.isOpReturn = function() { if (this.chunks[0] === Opcode('OP_RETURN').toNumber() && (this.chunks.length === 1 || - (this.chunks.length === 2 && this.chunks[1].buf && this.chunks[1].buf.length <= 40 && this.chunks[1].length === this.chunks.len))) { + (this.chunks.length === 2 && + this.chunks[1].buf && + this.chunks[1].buf.length <= 40 && + this.chunks[1].length === this.chunks.len))) { return true; } else { return false; @@ -179,7 +182,11 @@ Script.prototype.isOpReturn = function() { }; Script.prototype.isPublicKeyHashOut = function() { - if (this.chunks[0] === Opcode('OP_DUP').toNumber() && this.chunks[1] === Opcode('OP_HASH160').toNumber() && this.chunks[2].buf && this.chunks[3] === Opcode('OP_EQUALVERIFY').toNumber() && this.chunks[4] === Opcode('OP_CHECKSIG').toNumber()) { + if (this.chunks[0] === Opcode('OP_DUP').toNumber() && + this.chunks[1] === Opcode('OP_HASH160').toNumber() && + this.chunks[2].buf && + this.chunks[3] === Opcode('OP_EQUALVERIFY').toNumber() && + this.chunks[4] === Opcode('OP_CHECKSIG').toNumber()) { return true; } else { return false; @@ -187,7 +194,9 @@ Script.prototype.isPublicKeyHashOut = function() { }; Script.prototype.isPublicKeyHashIn = function() { - if (this.chunks.length === 2 && this.chunks[0].buf && this.chunks[1].buf) { + if (this.chunks.length === 2 && + this.chunks[0].buf && + this.chunks[1].buf) { return true; } else { return false; @@ -195,7 +204,11 @@ Script.prototype.isPublicKeyHashIn = function() { }; Script.prototype.isScriptHashOut = function() { - if (this.chunks.length === 3 && this.chunks[0] === Opcode('OP_HASH160').toNumber() && this.chunks[1].buf && this.chunks[1].buf.length === 20 && this.chunks[2] === Opcode('OP_EQUAL').toNumber()) { + if (this.chunks.length === 3 && + this.chunks[0] === Opcode('OP_HASH160').toNumber() && + this.chunks[1].buf && + this.chunks[1].buf.length === 20 && + this.chunks[2] === Opcode('OP_EQUAL').toNumber()) { return true; } else { return false; From 94f1afbad7e9d22952348a47cb962e1d485e5444 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Fri, 28 Nov 2014 11:45:42 -0300 Subject: [PATCH 04/20] fix tests --- .jsbeautifyrc | 20 ++++++++++++++++++++ lib/script.js | 39 +++++++++------------------------------ 2 files changed, 29 insertions(+), 30 deletions(-) create mode 100644 .jsbeautifyrc diff --git a/.jsbeautifyrc b/.jsbeautifyrc new file mode 100644 index 0000000..79f9b77 --- /dev/null +++ b/.jsbeautifyrc @@ -0,0 +1,20 @@ +{ + "indent_size": 2, + "indent_char": " ", + "indent_level": 0, + "indent_with_tabs": false, + "preserve_newlines": true, + "max_preserve_newlines": 10, + "jslint_happy": false, + "space_after_anon_function": true, + "brace_style": "collapse", + "keep_array_indentation": false, + "keep_function_indentation": false, + "space_before_conditional": true, + "break_chained_methods": false, + "eval_code": false, + "unescape_strings": false, + "wrap_line_length": 0, + "good-stuff": true + +} diff --git a/lib/script.js b/lib/script.js index 554a801..9991e0e 100644 --- a/lib/script.js +++ b/lib/script.js @@ -169,62 +169,41 @@ Script.prototype.toString = function() { }; Script.prototype.isOpReturn = function() { - if (this.chunks[0] === Opcode('OP_RETURN').toNumber() && + return (this.chunks[0] === Opcode('OP_RETURN').toNumber() && (this.chunks.length === 1 || (this.chunks.length === 2 && this.chunks[1].buf && this.chunks[1].buf.length <= 40 && - this.chunks[1].length === this.chunks.len))) { - return true; - } else { - return false; - } + this.chunks[1].length === this.chunks.len))); }; Script.prototype.isPublicKeyHashOut = function() { - if (this.chunks[0] === Opcode('OP_DUP').toNumber() && + return this.chunks[0] === Opcode('OP_DUP').toNumber() && this.chunks[1] === Opcode('OP_HASH160').toNumber() && this.chunks[2].buf && this.chunks[3] === Opcode('OP_EQUALVERIFY').toNumber() && - this.chunks[4] === Opcode('OP_CHECKSIG').toNumber()) { - return true; - } else { - return false; - } + this.chunks[4] === Opcode('OP_CHECKSIG').toNumber(); }; Script.prototype.isPublicKeyHashIn = function() { - if (this.chunks.length === 2 && + return !!(this.chunks.length === 2 && this.chunks[0].buf && - this.chunks[1].buf) { - return true; - } else { - return false; - } + this.chunks[1].buf); }; Script.prototype.isScriptHashOut = function() { - if (this.chunks.length === 3 && + return this.chunks.length === 3 && this.chunks[0] === Opcode('OP_HASH160').toNumber() && this.chunks[1].buf && this.chunks[1].buf.length === 20 && - this.chunks[2] === Opcode('OP_EQUAL').toNumber()) { - return true; - } else { - return false; - } + this.chunks[2] === Opcode('OP_EQUAL').toNumber(); }; //note that these are frequently indistinguishable from pubkeyhashin Script.prototype.isScriptHashIn = function() { - var allpush = this.chunks.every(function(chunk) { + return this.chunks.every(function(chunk) { return Buffer.isBuffer(chunk.buf); }); - if (allpush) { - return true; - } else { - return false; - } }; Script.prototype.add = function(obj) { From ebf97aa4bb8eb546e82930aeef18c41547c7889b Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Fri, 28 Nov 2014 11:48:06 -0300 Subject: [PATCH 05/20] fix condition style again --- lib/script.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/script.js b/lib/script.js index 9991e0e..defee06 100644 --- a/lib/script.js +++ b/lib/script.js @@ -126,7 +126,9 @@ Script.fromString = function(str) { } else { throw new Error('Invalid script'); } - } else if (opcodenum === Opcode.map.OP_PUSHDATA1 || opcodenum === Opcode.map.OP_PUSHDATA2 || opcodenum === Opcode.map.OP_PUSHDATA4) { + } else if (opcodenum === Opcode.map.OP_PUSHDATA1 || + opcodenum === Opcode.map.OP_PUSHDATA2 || + opcodenum === Opcode.map.OP_PUSHDATA4) { if (tokens[i + 2].slice(0, 2) !== '0x') { throw new Error('Pushdata data must start with 0x'); } From 4a6755d0d16c501440de4d4cc877594180b47d1c Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Fri, 28 Nov 2014 12:57:19 -0300 Subject: [PATCH 06/20] add Script#prepend() --- lib/address.js | 2 +- lib/script.js | 57 ++++++++++++++++++++++++++++++++++++++----------- test/mocha.opts | 1 + test/script.js | 56 ++++++++++++++++++++++++++++-------------------- 4 files changed, 79 insertions(+), 37 deletions(-) create mode 100644 test/mocha.opts diff --git a/lib/address.js b/lib/address.js index 295f277..d3159e2 100644 --- a/lib/address.js +++ b/lib/address.js @@ -227,7 +227,7 @@ Address._transformString = function(data, network, type){ * * Instantiate an address from a PublicKey instance * - * @param {String} data - An instance of PublicKey + * @param {PublicKey} data - An instance of PublicKey * @param {String} network - The network: 'livenet' or 'testnet' * @returns {Address} A new valid and frozen instance of an Address */ diff --git a/lib/script.js b/lib/script.js index defee06..e3be402 100644 --- a/lib/script.js +++ b/lib/script.js @@ -208,35 +208,66 @@ Script.prototype.isScriptHashIn = function() { }); }; +/** + * Adds a script element at the start of the script. + * @param {*} obj a string, number, Opcode, Bufer, or object to add + * @returns {Script} this script instance + */ +Script.prototype.prepend = function(obj) { + this._addByType(obj, true); + return this; +}; + +/** + * Adds a script element to the end of the script. + * + * @param {*} obj a string, number, Opcode, Bufer, or object to add + * @returns {Script} this script instance + * + */ Script.prototype.add = function(obj) { + this._addByType(obj, false); + return this; +}; + +Script.prototype._addByType = function(obj, prepend) { if (typeof obj === 'string') { - this._addOpcode(obj); + this._addOpcode(obj, prepend); } else if (typeof obj === 'number') { - this._addOpcode(obj); + this._addOpcode(obj, prepend); } else if (obj.constructor && obj.constructor.name && obj.constructor.name === 'Opcode') { - this._addOpcode(obj); + this._addOpcode(obj, prepend); } else if (Buffer.isBuffer(obj)) { - this._addBuffer(obj); + this._addBuffer(obj, prepend); } else if (typeof obj === 'object') { - this.chunks.push(obj); + this._insertAtPosition(obj, prepend); } else { throw new Error('Invalid script chunk'); } - return this; }; -Script.prototype._addOpcode = function(opcode) { +Script.prototype._insertAtPosition = function(op, prepend) { + if (prepend) { + this.chunks.unshift(op); + } else { + this.chunks.push(op); + } +}; + +Script.prototype._addOpcode = function(opcode, prepend) { + var op; if (typeof opcode === 'number') { - this.chunks.push(opcode); + op = opcode; } else if (opcode.constructor && opcode.constructor.name && opcode.constructor.name === 'Opcode') { - this.chunks.push(opcode.toNumber()); + op = opcode.toNumber(); } else { - this.chunks.push(Opcode(opcode).toNumber()); + op = Opcode(opcode).toNumber(); } + this._insertAtPosition(op, prepend); return this; }; -Script.prototype._addBuffer = function(buf) { +Script.prototype._addBuffer = function(buf, prepend) { var opcodenum; var len = buf.length; if (buf.length > 0 && buf.length < Opcode.map.OP_PUSHDATA1) { @@ -250,11 +281,11 @@ Script.prototype._addBuffer = function(buf) { } else { throw new Error('You can\'t push that much data'); } - this.chunks.push({ + this._insertAtPosition({ buf: buf, len: len, opcodenum: opcodenum - }); + }, prepend); return this; }; diff --git a/test/mocha.opts b/test/mocha.opts new file mode 100644 index 0000000..4a52320 --- /dev/null +++ b/test/mocha.opts @@ -0,0 +1 @@ +--recursive diff --git a/test/script.js b/test/script.js index 94fccbd..1a3fedf 100644 --- a/test/script.js +++ b/test/script.js @@ -251,37 +251,47 @@ describe('Script', function() { }); - describe('#add', function() { + describe('#add and #prepend', function() { it('should add these ops', function() { Script().add('OP_CHECKMULTISIG').toString().should.equal('OP_CHECKMULTISIG'); + Script().add('OP_1').add('OP_2').toString().should.equal('OP_1 OP_2'); + Script().add(new Opcode('OP_CHECKMULTISIG')).toString().should.equal('OP_CHECKMULTISIG'); Script().add(Opcode.map.OP_CHECKMULTISIG).toString().should.equal('OP_CHECKMULTISIG'); }); - }); + it('should prepend these ops', function() { + Script().prepend('OP_CHECKMULTISIG').toString().should.equal('OP_CHECKMULTISIG'); + Script().prepend('OP_1').prepend('OP_2').toString().should.equal('OP_2 OP_1'); + }); - it('should add these push data', function() { - var buf = new Buffer(1); - buf.fill(0); - Script().add(buf).toString().should.equal('1 0x00'); - buf = new Buffer(255); - buf.fill(0); - Script().add(buf).toString().should.equal('OP_PUSHDATA1 255 0x' + buf.toString('hex')); - buf = new Buffer(256); - buf.fill(0); - Script().add(buf).toString().should.equal('OP_PUSHDATA2 256 0x' + buf.toString('hex')); - buf = new Buffer(Math.pow(2, 16)); - buf.fill(0); - Script().add(buf).toString().should.equal('OP_PUSHDATA4 ' + Math.pow(2, 16) + ' 0x' + buf.toString('hex')); - }); + it('should add and prepend correctly', function() { + Script().add('OP_1').prepend('OP_2').add('OP_3').prepend('OP_4').toString() + .should.equal('OP_4 OP_2 OP_1 OP_3'); + }); - it('should add both pushdata and non-pushdata chunks', function() { - Script().add('OP_CHECKMULTISIG').toString().should.equal('OP_CHECKMULTISIG'); - Script().add(Opcode.map.OP_CHECKMULTISIG).toString().should.equal('OP_CHECKMULTISIG'); - var buf = new Buffer(1); - buf.fill(0); - Script().add(buf).toString().should.equal('1 0x00'); - }); + it('should add these push data', function() { + var buf = new Buffer(1); + buf.fill(0); + Script().add(buf).toString().should.equal('1 0x00'); + buf = new Buffer(255); + buf.fill(0); + Script().add(buf).toString().should.equal('OP_PUSHDATA1 255 0x' + buf.toString('hex')); + buf = new Buffer(256); + buf.fill(0); + Script().add(buf).toString().should.equal('OP_PUSHDATA2 256 0x' + buf.toString('hex')); + buf = new Buffer(Math.pow(2, 16)); + buf.fill(0); + Script().add(buf).toString().should.equal('OP_PUSHDATA4 ' + Math.pow(2, 16) + ' 0x' + buf.toString('hex')); + }); + it('should add both pushdata and non-pushdata chunks', function() { + Script().add('OP_CHECKMULTISIG').toString().should.equal('OP_CHECKMULTISIG'); + Script().add(Opcode.map.OP_CHECKMULTISIG).toString().should.equal('OP_CHECKMULTISIG'); + var buf = new Buffer(1); + buf.fill(0); + Script().add(buf).toString().should.equal('1 0x00'); + }); + }); }); From 350816ad408d4423ce4906753a206c9d38937d66 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Fri, 28 Nov 2014 14:54:19 -0300 Subject: [PATCH 07/20] opcode refactor: isSmallIntOp --- lib/opcode.js | 17 ++++++++++++-- test/opcode.js | 60 +++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 64 insertions(+), 13 deletions(-) diff --git a/lib/opcode.js b/lib/opcode.js index 323c154..ccd6535 100644 --- a/lib/opcode.js +++ b/lib/opcode.js @@ -1,8 +1,9 @@ 'use strict'; function Opcode(num) { - if (!(this instanceof Opcode)) + if (!(this instanceof Opcode)) { return new Opcode(num); + } if (typeof num === 'number') { this.num = num; @@ -39,8 +40,9 @@ Opcode.prototype.fromString = function(str) { Opcode.prototype.toString = function() { var str = Opcode.reverseMap[this.num]; - if (typeof str === 'undefined') + if (typeof str === 'undefined') { throw new Error('Opcode does not have a string representation'); + } return str; }; @@ -191,4 +193,15 @@ for (var k in Opcode.map) { } } +/** + * @returns true if opcode is one of OP_0, OP_1, ..., OP_16 + */ +Opcode.isSmallIntOp = function(opcode) { + if (opcode instanceof Opcode) { + opcode = opcode.toNumber(); + } + return ((opcode === Opcode.map.OP_0) || + ((opcode >= Opcode.map.OP_1) && (opcode <= Opcode.map.OP_16))); +}; + module.exports = Opcode; diff --git a/test/opcode.js b/test/opcode.js index a01ecfd..c9d8307 100644 --- a/test/opcode.js +++ b/test/opcode.js @@ -11,13 +11,13 @@ describe('Opcode', function() { var opcode = new Opcode(5); should.exist(opcode); }); - + it('should convert to a string with this handy syntax', function() { Opcode(0).toString().should.equal('OP_0'); Opcode(96).toString().should.equal('OP_16'); Opcode(97).toString().should.equal('OP_NOP'); }); - + it('should convert to a number with this handy syntax', function() { Opcode('OP_0').toNumber().should.equal(0); Opcode('OP_16').toNumber().should.equal(96); @@ -25,37 +25,37 @@ describe('Opcode', function() { }); describe('#fromNumber', function() { - + it('should work for 0', function() { Opcode().fromNumber(0).num.should.equal(0); }); }); - + describe('#toNumber', function() { - + it('should work for 0', function() { Opcode().fromNumber(0).toNumber().should.equal(0); }); }); - + describe('#fromString', function() { - + it('should work for OP_0', function() { Opcode().fromString('OP_0').num.should.equal(0); }); }); - + describe('#toString', function() { - + it('should work for OP_0', function() { Opcode().fromString('OP_0').toString().should.equal('OP_0'); }); }); - + describe('@map', function() { it('should have a map containing 116 elements', function() { @@ -65,12 +65,50 @@ describe('Opcode', function() { }); describe('@reverseMap', function() { - + it('should exist and have op 185', function() { should.exist(Opcode.reverseMap); Opcode.reverseMap[185].should.equal('OP_NOP10'); }); }); + var smallints = [ + Opcode('OP_0'), + Opcode('OP_1'), + Opcode('OP_2'), + Opcode('OP_3'), + Opcode('OP_4'), + Opcode('OP_5'), + Opcode('OP_6'), + Opcode('OP_7'), + Opcode('OP_8'), + Opcode('OP_9'), + Opcode('OP_10'), + Opcode('OP_11'), + Opcode('OP_12'), + Opcode('OP_13'), + Opcode('OP_14'), + Opcode('OP_15'), + Opcode('OP_16') + ]; + + describe('@isSmallIntOp', function() { + var testSmallInt = function() { + Opcode.isSmallIntOp(this).should.equal(true); + }; + for (var i = 0; i < smallints.length; i++) { + var op = smallints[i]; + it('should work for small int ' + op, testSmallInt.bind(op)); + } + + it('should work for non-small ints', function() { + Opcode.isSmallIntOp(Opcode('OP_RETURN')).should.equal(false); + Opcode.isSmallIntOp(Opcode('OP_CHECKSIG')).should.equal(false); + Opcode.isSmallIntOp(Opcode('OP_IF')).should.equal(false); + Opcode.isSmallIntOp(Opcode('OP_NOP')).should.equal(false); + }); + + }); + }); From 96e1451d280f8cf0f05db33f45f6764605a6e8a6 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Fri, 28 Nov 2014 16:19:07 -0300 Subject: [PATCH 08/20] add script.isMultisigOut --- lib/script.js | 53 +++++++++++++++++++++++++++++++++++++++++--------- package.json | 5 ++++- test/script.js | 22 +++++++++++++++++++++ 3 files changed, 70 insertions(+), 10 deletions(-) diff --git a/lib/script.js b/lib/script.js index e3be402..6dac25d 100644 --- a/lib/script.js +++ b/lib/script.js @@ -170,15 +170,13 @@ Script.prototype.toString = function() { return str.substr(0, str.length - 1); }; -Script.prototype.isOpReturn = function() { - return (this.chunks[0] === Opcode('OP_RETURN').toNumber() && - (this.chunks.length === 1 || - (this.chunks.length === 2 && - this.chunks[1].buf && - this.chunks[1].buf.length <= 40 && - this.chunks[1].length === this.chunks.len))); -}; + +// script classification methods + +/** + * @returns true if this is a pay to pubkey hash output script + */ Script.prototype.isPublicKeyHashOut = function() { return this.chunks[0] === Opcode('OP_DUP').toNumber() && this.chunks[1] === Opcode('OP_HASH160').toNumber() && @@ -187,12 +185,18 @@ Script.prototype.isPublicKeyHashOut = function() { this.chunks[4] === Opcode('OP_CHECKSIG').toNumber(); }; +/** + * @returns true if this is a pay to public key hash input script + */ Script.prototype.isPublicKeyHashIn = function() { return !!(this.chunks.length === 2 && this.chunks[0].buf && this.chunks[1].buf); }; +/** + * @returns true if this is a p2sh output script + */ Script.prototype.isScriptHashOut = function() { return this.chunks.length === 3 && this.chunks[0] === Opcode('OP_HASH160').toNumber() && @@ -201,13 +205,44 @@ Script.prototype.isScriptHashOut = function() { this.chunks[2] === Opcode('OP_EQUAL').toNumber(); }; -//note that these are frequently indistinguishable from pubkeyhashin +/** + * @returns true if this is a p2sh input script + * Note that these are frequently indistinguishable from pubkeyhashin + */ Script.prototype.isScriptHashIn = function() { return this.chunks.every(function(chunk) { return Buffer.isBuffer(chunk.buf); }); }; +/** + * @returns true if this is a mutlsig output script + */ +Script.prototype.isMultisigOut = function() { + return (this.chunks.length > 3 && + Opcode.isSmallIntOp(this.chunks[0]) && + this.chunks.slice(1, this.chunks.length - 2).every(function(obj) { + return obj.buf && Buffer.isBuffer(obj.buf); + }) && + Opcode.isSmallIntOp(this.chunks[this.chunks.length - 2]) && + this.chunks[this.chunks.length - 1] === Opcode.map.OP_CHECKMULTISIG); +}; + +/** + * @returns true if this is an OP_RETURN data script + */ +Script.prototype.isOpReturn = function() { + return (this.chunks[0] === Opcode('OP_RETURN').toNumber() && + (this.chunks.length === 1 || + (this.chunks.length === 2 && + this.chunks[1].buf && + this.chunks[1].buf.length <= 40 && + this.chunks[1].length === this.chunks.len))); +}; + + +// Script construction methods + /** * Adds a script element at the start of the script. * @param {*} obj a string, number, Opcode, Bufer, or object to add diff --git a/package.json b/package.json index 6567381..5ae6407 100644 --- a/package.json +++ b/package.json @@ -96,8 +96,11 @@ "mocha": "~2.0.1", "run-sequence": "^1.0.2", "karma": "^0.12.28", + "karma-firefox-launcher": "^0.1.3", "karma-mocha": "^0.1.9", - "karma-firefox-launcher": "^0.1.3" + "lodash": "^2.4.1", + "mocha": "~2.0.1", + "run-sequence": "^1.0.2" }, "license": "MIT" } diff --git a/test/script.js b/test/script.js index 1a3fedf..2be11f1 100644 --- a/test/script.js +++ b/test/script.js @@ -251,6 +251,28 @@ describe('Script', function() { }); + describe('#isMultisigOut', function() { + it('should classify known multisig out 1 as multisig out', function() { + Script('OP_2 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 OP_2 OP_CHECKMULTISIG').isMultisigOut().should.equal(true); + }); + it('should classify known multisig out 2 as multisig out', function() { + Script('OP_1 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 OP_2 OP_CHECKMULTISIG').isMultisigOut().should.equal(true); + }); + it('should classify known multisig out 3 as multisig out', function() { + Script('OP_2 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 21 0x03363d90d447b00c9c99ceac05b6262ee053441c7e55552ffe526bad8f83ff4640 OP_3 OP_CHECKMULTISIG').isMultisigOut().should.equal(true); + }); + + it('should classify non-multisig out 1 as non-multisig out', function() { + Script('OP_2 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 OP_2 OP_CHECKMULTISIG OP_EQUAL').isMultisigOut().should.equal(false); + }); + it('should classify non-multisig out 2 as non-multisig out', function() { + Script('OP_2').isMultisigOut().should.equal(false); + }); + it('should classify non-multisig out 3 as non-multisig out', function() { + Script('OP_2 OP_2 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 OP_2 OP_CHECKMULTISIG OP_EQUAL').isMultisigOut().should.equal(false); + }); + }); + describe('#add and #prepend', function() { it('should add these ops', function() { From 72b5dcc6afeb504968688115cf441c270ce1ad44 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Fri, 28 Nov 2014 16:57:33 -0300 Subject: [PATCH 09/20] add Script.isMultisigIn --- lib/script.js | 17 +++++++++++++++++ test/script.js | 26 +++++++++++++++++++------- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/lib/script.js b/lib/script.js index 6dac25d..7815cb4 100644 --- a/lib/script.js +++ b/lib/script.js @@ -228,6 +228,23 @@ Script.prototype.isMultisigOut = function() { this.chunks[this.chunks.length - 1] === Opcode.map.OP_CHECKMULTISIG); }; + +/** + * @returns true if this is a mutlsig input script + */ +Script.prototype.isMultisigIn = function() { + return this.chunks[0] === 0 && + this.chunks.slice(1, this.chunks.length).every(function(obj) { + return obj.buf && + Buffer.isBuffer(obj.buf) && + obj.buf.length === 0x47; + }); +}; + +/** + * @returns true if this is a mutlsig input script + */ + /** * @returns true if this is an OP_RETURN data script */ diff --git a/test/script.js b/test/script.js index 2be11f1..8e0a064 100644 --- a/test/script.js +++ b/test/script.js @@ -252,24 +252,36 @@ describe('Script', function() { }); describe('#isMultisigOut', function() { - it('should classify known multisig out 1 as multisig out', function() { + it('should classify known multisig out 1', function() { Script('OP_2 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 OP_2 OP_CHECKMULTISIG').isMultisigOut().should.equal(true); }); - it('should classify known multisig out 2 as multisig out', function() { + it('should classify known multisig out 2', function() { Script('OP_1 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 OP_2 OP_CHECKMULTISIG').isMultisigOut().should.equal(true); }); - it('should classify known multisig out 3 as multisig out', function() { + it('should classify known multisig out 3', function() { Script('OP_2 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 21 0x03363d90d447b00c9c99ceac05b6262ee053441c7e55552ffe526bad8f83ff4640 OP_3 OP_CHECKMULTISIG').isMultisigOut().should.equal(true); }); - it('should classify non-multisig out 1 as non-multisig out', function() { + it('should classify non-multisig out 1', function() { Script('OP_2 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 OP_2 OP_CHECKMULTISIG OP_EQUAL').isMultisigOut().should.equal(false); }); - it('should classify non-multisig out 2 as non-multisig out', function() { + it('should classify non-multisig out 2', function() { Script('OP_2').isMultisigOut().should.equal(false); }); - it('should classify non-multisig out 3 as non-multisig out', function() { - Script('OP_2 OP_2 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 OP_2 OP_CHECKMULTISIG OP_EQUAL').isMultisigOut().should.equal(false); + }); + + describe('#isMultisigIn', function() { + it('should classify multisig in 1', function() { + Script('OP_0 0x47 0x3044022002a27769ee33db258bdf7a3792e7da4143ec4001b551f73e6a190b8d1bde449d02206742c56ccd94a7a2e16ca52fc1ae4a0aa122b0014a867a80de104f9cb18e472c01').isMultisigIn().should.equal(true); + }); + it('should classify multisig in 2', function() { + Script('OP_0 0x47 0x3044022002a27769ee33db258bdf7a3792e7da4143ec4001b551f73e6a190b8d1bde449d02206742c56ccd94a7a2e16ca52fc1ae4a0aa122b0014a867a80de104f9cb18e472c01 0x47 30450220357011fd3b3ad2b8f2f2d01e05dc6108b51d2a245b4ef40c112d6004596f0475022100a8208c93a39e0c366b983f9a80bfaf89237fcd64ca543568badd2d18ee2e1d7501').isMultisigIn().should.equal(true); + }); + it('should classify non-multisig in 1', function() { + Script('0x47 0x3044022002a27769ee33db258bdf7a3792e7da4143ec4001b551f73e6a190b8d1bde449d02206742c56ccd94a7a2e16ca52fc1ae4a0aa122b0014a867a80de104f9cb18e472c01').isMultisigIn().should.equal(false); + }); + it('should classify non-multisig in 2', function() { + Script('OP_0 0x47 0x3044022002a27769ee33db258bdf7a3792e7da4143ec4001b551f73e6a190b8d1bde449d02206742c56ccd94a7a2e16ca52fc1ae4a0aa122b0014a867a80de104f9cb18e472c01 OP_0').isMultisigIn().should.equal(false); }); }); From 293a3299d4460023e294e53c1a3b2f341cee9315 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Fri, 28 Nov 2014 18:26:05 -0300 Subject: [PATCH 10/20] add classify tests --- lib/script.js | 55 ++++++++++++++++++++++++++++++++++++++++++++++---- test/script.js | 46 +++++++++++++++++++++++++---------------- 2 files changed, 80 insertions(+), 21 deletions(-) diff --git a/lib/script.js b/lib/script.js index 7815cb4..c53d675 100644 --- a/lib/script.js +++ b/lib/script.js @@ -194,6 +194,21 @@ Script.prototype.isPublicKeyHashIn = function() { this.chunks[1].buf); }; +/** + * @returns true if this is a public key output script + */ +Script.prototype.isPublicKeyOut = function() { + return false; +}; + +/** + * @returns true if this is a pay to public key input script + */ +Script.prototype.isPublicKeyIn = function() { + return false; +}; + + /** * @returns true if this is a p2sh output script */ @@ -241,10 +256,6 @@ Script.prototype.isMultisigIn = function() { }); }; -/** - * @returns true if this is a mutlsig input script - */ - /** * @returns true if this is an OP_RETURN data script */ @@ -258,6 +269,42 @@ Script.prototype.isOpReturn = function() { }; +Script.types = {}; +Script.types.UNKNOWN = 'Unknown'; +Script.types.PUBKEY_OUT = 'Pay to public key'; +Script.types.PUBKEY_IN = 'Spend from public key'; +Script.types.PUBKEYHASH_OUT = 'Pay to public key hash'; +Script.types.PUBKEYHASH_IN = 'Spend from public key hash'; +Script.types.SCRIPTHASH_OUT = 'Pay to script hash'; +Script.types.SCRIPTHASH_IN = 'Spend from script hash'; +Script.types.MULTISIG_OUT = 'Pay to multisig'; +Script.types.MULTISIG_IN = 'Spend from multisig'; +Script.types.OP_RETURN = 'Data push'; + +Script.identifiers = {}; +Script.identifiers.PUBKEY_OUT = Script.prototype.isPublicKeyOut; +Script.identifiers.PUBKEY_IN = Script.prototype.isPublicKeyIn; +Script.identifiers.PUBKEYHASH_OUT = Script.prototype.isPublicKeyHashOut; +Script.identifiers.PUBKEYHASH_IN = Script.prototype.isPublicKeyHashIn; +Script.identifiers.SCRIPTHASH_OUT = Script.prototype.isScriptHashOut; +Script.identifiers.SCRIPTHASH_IN = Script.prototype.isScriptHashIn; +Script.identifiers.MULTISIG_OUT = Script.prototype.isMultisigOut; +Script.identifiers.MULTISIG_IN = Script.prototype.isMultisigIn; +Script.identifiers.OP_RETURN = Script.prototype.isOpReturn; + +/** + * @returns {object} The Script type if it is a known form, + * or Script.UNKNOWN if it isn't + */ +Script.prototype.classify = function() { + for (var type in Script.identifiers) { + if (Script.identifiers[type].bind(this)()) { + return Script.types[type]; + } + } + return Script.types.UNKNOWN; +}; + // Script construction methods /** diff --git a/test/script.js b/test/script.js index 8e0a064..143b10d 100644 --- a/test/script.js +++ b/test/script.js @@ -204,11 +204,11 @@ describe('Script', function() { describe('#isPublicKeyHashIn', function() { - it('should classify this known pubkeyhashin', function() { + it('should identify this known pubkeyhashin', function() { Script('73 0x3046022100bb3c194a30e460d81d34be0a230179c043a656f67e3c5c8bf47eceae7c4042ee0221008bf54ca11b2985285be0fd7a212873d243e6e73f5fad57e8eb14c4f39728b8c601 65 0x04e365859b3c78a8b7c202412b949ebca58e147dba297be29eee53cd3e1d300a6419bc780cc9aec0dc94ed194e91c8f6433f1b781ee00eac0ead2aae1e8e0712c6').isPublicKeyHashIn().should.equal(true); }); - it('should classify this known non-pubkeyhashin', function() { + it('should identify this known non-pubkeyhashin', function() { Script('73 0x3046022100bb3c194a30e460d81d34be0a230179c043a656f67e3c5c8bf47eceae7c4042ee0221008bf54ca11b2985285be0fd7a212873d243e6e73f5fad57e8eb14c4f39728b8c601 65 0x04e365859b3c78a8b7c202412b949ebca58e147dba297be29eee53cd3e1d300a6419bc780cc9aec0dc94ed194e91c8f6433f1b781ee00eac0ead2aae1e8e0712c6 OP_CHECKSIG').isPublicKeyHashIn().should.equal(false); }); @@ -216,11 +216,11 @@ describe('Script', function() { describe('#isPublicKeyHashOut', function() { - it('should classify this known pubkeyhashout as pubkeyhashout', function() { + it('should identify this known pubkeyhashout as pubkeyhashout', function() { Script('OP_DUP OP_HASH160 20 0000000000000000000000000000000000000000 OP_EQUALVERIFY OP_CHECKSIG').isPublicKeyHashOut().should.equal(true); }); - it('should classify this known non-pubkeyhashout as not pubkeyhashout', function() { + it('should identify this known non-pubkeyhashout as not pubkeyhashout', function() { Script('OP_DUP OP_HASH160 20 0000000000000000000000000000000000000000').isPublicKeyHashOut().should.equal(false); }); @@ -228,11 +228,11 @@ describe('Script', function() { describe('#isScripthashIn', function() { - it('should classify this known scripthashin', function() { + it('should identify this known scripthashin', function() { Script('20 0000000000000000000000000000000000000000').isScriptHashIn().should.equal(true); }); - it('should classify this known non-scripthashin', function() { + it('should identify this known non-scripthashin', function() { Script('20 0000000000000000000000000000000000000000 OP_CHECKSIG').isScriptHashIn().should.equal(false); }); @@ -240,11 +240,11 @@ describe('Script', function() { describe('#isScripthashOut', function() { - it('should classify this known pubkeyhashout as pubkeyhashout', function() { + it('should identify this known pubkeyhashout as pubkeyhashout', function() { Script('OP_HASH160 20 0x0000000000000000000000000000000000000000 OP_EQUAL').isScriptHashOut().should.equal(true); }); - it('should classify these known non-pubkeyhashout as not pubkeyhashout', function() { + it('should identify these known non-pubkeyhashout as not pubkeyhashout', function() { Script('OP_HASH160 20 0x0000000000000000000000000000000000000000 OP_EQUAL OP_EQUAL').isScriptHashOut().should.equal(false); Script('OP_HASH160 21 0x000000000000000000000000000000000000000000 OP_EQUAL').isScriptHashOut().should.equal(false); }); @@ -252,39 +252,51 @@ describe('Script', function() { }); describe('#isMultisigOut', function() { - it('should classify known multisig out 1', function() { + it('should identify known multisig out 1', function() { Script('OP_2 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 OP_2 OP_CHECKMULTISIG').isMultisigOut().should.equal(true); }); - it('should classify known multisig out 2', function() { + it('should identify known multisig out 2', function() { Script('OP_1 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 OP_2 OP_CHECKMULTISIG').isMultisigOut().should.equal(true); }); - it('should classify known multisig out 3', function() { + it('should identify known multisig out 3', function() { Script('OP_2 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 21 0x03363d90d447b00c9c99ceac05b6262ee053441c7e55552ffe526bad8f83ff4640 OP_3 OP_CHECKMULTISIG').isMultisigOut().should.equal(true); }); - it('should classify non-multisig out 1', function() { + it('should identify non-multisig out 1', function() { Script('OP_2 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 OP_2 OP_CHECKMULTISIG OP_EQUAL').isMultisigOut().should.equal(false); }); - it('should classify non-multisig out 2', function() { + it('should identify non-multisig out 2', function() { Script('OP_2').isMultisigOut().should.equal(false); }); }); describe('#isMultisigIn', function() { - it('should classify multisig in 1', function() { + it('should identify multisig in 1', function() { Script('OP_0 0x47 0x3044022002a27769ee33db258bdf7a3792e7da4143ec4001b551f73e6a190b8d1bde449d02206742c56ccd94a7a2e16ca52fc1ae4a0aa122b0014a867a80de104f9cb18e472c01').isMultisigIn().should.equal(true); }); - it('should classify multisig in 2', function() { + it('should identify multisig in 2', function() { Script('OP_0 0x47 0x3044022002a27769ee33db258bdf7a3792e7da4143ec4001b551f73e6a190b8d1bde449d02206742c56ccd94a7a2e16ca52fc1ae4a0aa122b0014a867a80de104f9cb18e472c01 0x47 30450220357011fd3b3ad2b8f2f2d01e05dc6108b51d2a245b4ef40c112d6004596f0475022100a8208c93a39e0c366b983f9a80bfaf89237fcd64ca543568badd2d18ee2e1d7501').isMultisigIn().should.equal(true); }); - it('should classify non-multisig in 1', function() { + it('should identify non-multisig in 1', function() { Script('0x47 0x3044022002a27769ee33db258bdf7a3792e7da4143ec4001b551f73e6a190b8d1bde449d02206742c56ccd94a7a2e16ca52fc1ae4a0aa122b0014a867a80de104f9cb18e472c01').isMultisigIn().should.equal(false); }); - it('should classify non-multisig in 2', function() { + it('should identify non-multisig in 2', function() { Script('OP_0 0x47 0x3044022002a27769ee33db258bdf7a3792e7da4143ec4001b551f73e6a190b8d1bde449d02206742c56ccd94a7a2e16ca52fc1ae4a0aa122b0014a867a80de104f9cb18e472c01 OP_0').isMultisigIn().should.equal(false); }); }); + describe.only('#classify', function() { + it('should classify public key hash out', function() { + Script('OP_DUP OP_HASH160 20 0000000000000000000000000000000000000000 OP_EQUALVERIFY OP_CHECKSIG').classify().should.equal(Script.types.PUBKEYHASH_OUT); + }); + it('should classify public key hash in', function() { + Script('47 0x3044022077a8d81e656c4a1c1721e68ce35fa0b27f13c342998e75854858c12396a15ffa02206378a8c6959283c008c87a14a9c0ada5cf3934ac5ee29f1fef9cac6969783e9801 21 0x03993c230da7dabb956292851ae755f971c50532efc095a16bee07f83ab9d262df').classify().should.equal(Script.types.PUBKEYHASH_IN); + }); + it('should classify OP_RETURN', function() { + Script('OP_RETURN 1 0x01').classify().should.equal(Script.types.OP_RETURN); + }); + }); + describe('#add and #prepend', function() { it('should add these ops', function() { From 9d6ff4dc440f8dd1280941beec86a8a7f49a911d Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Fri, 28 Nov 2014 18:28:12 -0300 Subject: [PATCH 11/20] add classify tests --- test/script.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/script.js b/test/script.js index 143b10d..d96ba27 100644 --- a/test/script.js +++ b/test/script.js @@ -292,6 +292,18 @@ describe('Script', function() { it('should classify public key hash in', function() { Script('47 0x3044022077a8d81e656c4a1c1721e68ce35fa0b27f13c342998e75854858c12396a15ffa02206378a8c6959283c008c87a14a9c0ada5cf3934ac5ee29f1fef9cac6969783e9801 21 0x03993c230da7dabb956292851ae755f971c50532efc095a16bee07f83ab9d262df').classify().should.equal(Script.types.PUBKEYHASH_IN); }); + it('should classify script hash out', function() { + Script('').classify().should.equal(Script.types.SCRIPTHASH_OUT); + }); + it('should classify script hash in', function() { + Script('').classify().should.equal(Script.types.SCRIPTHASH_IN); + }); + it('should classify MULTISIG out', function() { + Script('').classify().should.equal(Script.types.MULTISIG_OUT); + }); + it('should classify MULTISIG in', function() { + Script('').classify().should.equal(Script.types.MULTISIG_IN); + }); it('should classify OP_RETURN', function() { Script('OP_RETURN 1 0x01').classify().should.equal(Script.types.OP_RETURN); }); From 7d9151abc662bfc1ab67a232c1e7e50f34020481 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Fri, 28 Nov 2014 18:44:23 -0300 Subject: [PATCH 12/20] fix classify tests --- lib/script.js | 14 +++++++++++--- test/script.js | 5 +++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/script.js b/lib/script.js index c53d675..6b827b1 100644 --- a/lib/script.js +++ b/lib/script.js @@ -225,9 +225,17 @@ Script.prototype.isScriptHashOut = function() { * Note that these are frequently indistinguishable from pubkeyhashin */ Script.prototype.isScriptHashIn = function() { - return this.chunks.every(function(chunk) { - return Buffer.isBuffer(chunk.buf); - }); + var chunk = this.chunks[this.chunks.length - 1]; + if (!chunk) { + return false; + } + var scriptBuf = chunk.buf; + if (!scriptBuf) { + return false; + } + var redeemScript = new Script(scriptBuf); + var type = redeemScript.classify(); + return type !== Script.types.UNKNOWN; }; /** diff --git a/test/script.js b/test/script.js index d96ba27..c3cc169 100644 --- a/test/script.js +++ b/test/script.js @@ -293,10 +293,11 @@ describe('Script', function() { Script('47 0x3044022077a8d81e656c4a1c1721e68ce35fa0b27f13c342998e75854858c12396a15ffa02206378a8c6959283c008c87a14a9c0ada5cf3934ac5ee29f1fef9cac6969783e9801 21 0x03993c230da7dabb956292851ae755f971c50532efc095a16bee07f83ab9d262df').classify().should.equal(Script.types.PUBKEYHASH_IN); }); it('should classify script hash out', function() { - Script('').classify().should.equal(Script.types.SCRIPTHASH_OUT); + Script('OP_HASH160 20 0x0000000000000000000000000000000000000000 OP_EQUAL').classify().should.equal(Script.types.SCRIPTHASH_OUT); }); it('should classify script hash in', function() { - Script('').classify().should.equal(Script.types.SCRIPTHASH_IN); + var x = Script('OP_0 73 0x30460221008ca148504190c10eea7f5f9c283c719a37be58c3ad617928011a1bb9570901d2022100ced371a23e86af6f55ff4ce705c57d2721a09c4d192ca39d82c4239825f75a9801 72 0x30450220357011fd3b3ad2b8f2f2d01e05dc6108b51d2a245b4ef40c112d6004596f0475022100a8208c93a39e0c366b983f9a80bfaf89237fcd64ca543568badd2d18ee2e1d7501 OP_PUSHDATA1 105 0x5221024c02dff2f0b8263a562a69ec875b2c95ffad860f428acf2f9e8c6492bd067d362103546324a1351a6b601c623b463e33b6103ca444707d5b278ece1692f1aa7724a42103b1ad3b328429450069cc3f9fa80d537ee66ba1120e93f3f185a5bf686fb51e0a53ae'); + x.classify().should.equal(Script.types.SCRIPTHASH_IN); }); it('should classify MULTISIG out', function() { Script('').classify().should.equal(Script.types.MULTISIG_OUT); From 901d55d1298c3c5acb951b75e3ce08e65a628e51 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Fri, 28 Nov 2014 18:47:02 -0300 Subject: [PATCH 13/20] fix classify tests --- test/script.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/script.js b/test/script.js index c3cc169..2b876bf 100644 --- a/test/script.js +++ b/test/script.js @@ -300,14 +300,20 @@ describe('Script', function() { x.classify().should.equal(Script.types.SCRIPTHASH_IN); }); it('should classify MULTISIG out', function() { - Script('').classify().should.equal(Script.types.MULTISIG_OUT); + Script('OP_2 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 OP_2 OP_CHECKMULTISIG').classify().should.equal(Script.types.MULTISIG_OUT); }); it('should classify MULTISIG in', function() { - Script('').classify().should.equal(Script.types.MULTISIG_IN); + Script('OP_0 0x47 0x3044022002a27769ee33db258bdf7a3792e7da4143ec4001b551f73e6a190b8d1bde449d02206742c56ccd94a7a2e16ca52fc1ae4a0aa122b0014a867a80de104f9cb18e472c01').classify().should.equal(Script.types.MULTISIG_IN); }); it('should classify OP_RETURN', function() { Script('OP_RETURN 1 0x01').classify().should.equal(Script.types.OP_RETURN); }); + it('should classify public key out', function() { + Script('').classify().should.equal(Script.types.PUBKEY_OUT); + }); + it('should classify public key in', function() { + Script('').classify().should.equal(Script.types.PUBKEY_IN); + }); }); describe('#add and #prepend', function() { From ed0fc6298c75887d7a80083188649daff0d3003a Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Fri, 28 Nov 2014 18:58:39 -0300 Subject: [PATCH 14/20] fix classify tests --- lib/script.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/script.js b/lib/script.js index 6b827b1..ccc80f5 100644 --- a/lib/script.js +++ b/lib/script.js @@ -191,7 +191,9 @@ Script.prototype.isPublicKeyHashOut = function() { Script.prototype.isPublicKeyHashIn = function() { return !!(this.chunks.length === 2 && this.chunks[0].buf && - this.chunks[1].buf); + this.chunks[0].buf.length === 0x47 && + this.chunks[1].buf && + this.chunks[1].buf.length === 0x21); }; /** @@ -225,6 +227,9 @@ Script.prototype.isScriptHashOut = function() { * Note that these are frequently indistinguishable from pubkeyhashin */ Script.prototype.isScriptHashIn = function() { + if (this.chunks.length === 0) { + return false; + } var chunk = this.chunks[this.chunks.length - 1]; if (!chunk) { return false; @@ -233,8 +238,11 @@ Script.prototype.isScriptHashIn = function() { if (!scriptBuf) { return false; } + console.log(this.toString()); var redeemScript = new Script(scriptBuf); var type = redeemScript.classify(); + console.log(redeemScript.toString()); + console.log(redeemScript.classify()); return type !== Script.types.UNKNOWN; }; @@ -294,11 +302,11 @@ Script.identifiers.PUBKEY_OUT = Script.prototype.isPublicKeyOut; Script.identifiers.PUBKEY_IN = Script.prototype.isPublicKeyIn; Script.identifiers.PUBKEYHASH_OUT = Script.prototype.isPublicKeyHashOut; Script.identifiers.PUBKEYHASH_IN = Script.prototype.isPublicKeyHashIn; -Script.identifiers.SCRIPTHASH_OUT = Script.prototype.isScriptHashOut; -Script.identifiers.SCRIPTHASH_IN = Script.prototype.isScriptHashIn; Script.identifiers.MULTISIG_OUT = Script.prototype.isMultisigOut; Script.identifiers.MULTISIG_IN = Script.prototype.isMultisigIn; Script.identifiers.OP_RETURN = Script.prototype.isOpReturn; +Script.identifiers.SCRIPTHASH_OUT = Script.prototype.isScriptHashOut; +Script.identifiers.SCRIPTHASH_IN = Script.prototype.isScriptHashIn; /** * @returns {object} The Script type if it is a known form, From 3e2bcaa297ca9e1eb54df262054eda6403dce48e Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Mon, 1 Dec 2014 11:30:16 -0300 Subject: [PATCH 15/20] more verbose invalid script error --- lib/script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/script.js b/lib/script.js index ccc80f5..6bafe8c 100644 --- a/lib/script.js +++ b/lib/script.js @@ -124,7 +124,7 @@ Script.fromString = function(str) { }); i = i + 2; } else { - throw new Error('Invalid script'); + throw new Error('Invalid script: '+JSON.stringify(str)); } } else if (opcodenum === Opcode.map.OP_PUSHDATA1 || opcodenum === Opcode.map.OP_PUSHDATA2 || From 87c40193b9d031fdc0ce480621d08e1431041339 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Mon, 1 Dec 2014 11:49:44 -0300 Subject: [PATCH 16/20] add pubkey in pubkey out script types --- lib/script.js | 14 ++++++++------ test/script.js | 6 +++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/script.js b/lib/script.js index 6bafe8c..8260ff6 100644 --- a/lib/script.js +++ b/lib/script.js @@ -124,7 +124,7 @@ Script.fromString = function(str) { }); i = i + 2; } else { - throw new Error('Invalid script: '+JSON.stringify(str)); + throw new Error('Invalid script: ' + JSON.stringify(str)); } } else if (opcodenum === Opcode.map.OP_PUSHDATA1 || opcodenum === Opcode.map.OP_PUSHDATA2 || @@ -200,14 +200,19 @@ Script.prototype.isPublicKeyHashIn = function() { * @returns true if this is a public key output script */ Script.prototype.isPublicKeyOut = function() { - return false; + return this.chunks.length === 2 && + Buffer.isBuffer(this.chunks[0].buf) && + this.chunks[0].buf.length === 0x41 && + this.chunks[1] === Opcode('OP_CHECKSIG').toNumber(); }; /** * @returns true if this is a pay to public key input script */ Script.prototype.isPublicKeyIn = function() { - return false; + return this.chunks.length === 1 && + Buffer.isBuffer(this.chunks[0].buf) && + this.chunks[0].buf.length === 0x47; }; @@ -238,11 +243,8 @@ Script.prototype.isScriptHashIn = function() { if (!scriptBuf) { return false; } - console.log(this.toString()); var redeemScript = new Script(scriptBuf); var type = redeemScript.classify(); - console.log(redeemScript.toString()); - console.log(redeemScript.classify()); return type !== Script.types.UNKNOWN; }; diff --git a/test/script.js b/test/script.js index 2b876bf..5cec12a 100644 --- a/test/script.js +++ b/test/script.js @@ -285,7 +285,7 @@ describe('Script', function() { }); }); - describe.only('#classify', function() { + describe('#classify', function() { it('should classify public key hash out', function() { Script('OP_DUP OP_HASH160 20 0000000000000000000000000000000000000000 OP_EQUALVERIFY OP_CHECKSIG').classify().should.equal(Script.types.PUBKEYHASH_OUT); }); @@ -309,10 +309,10 @@ describe('Script', function() { Script('OP_RETURN 1 0x01').classify().should.equal(Script.types.OP_RETURN); }); it('should classify public key out', function() { - Script('').classify().should.equal(Script.types.PUBKEY_OUT); + Script('41 0x0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 OP_CHECKSIG').classify().should.equal(Script.types.PUBKEY_OUT); }); it('should classify public key in', function() { - Script('').classify().should.equal(Script.types.PUBKEY_IN); + Script('47 0x3044022007415aa37ce7eaa6146001ac8bdefca0ddcba0e37c5dc08c4ac99392124ebac802207d382307fd53f65778b07b9c63b6e196edeadf0be719130c5db21ff1e700d67501').classify().should.equal(Script.types.PUBKEY_IN); }); }); From c4cc6ba56da3d91aa931b78c55fd32e2d2459515 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Mon, 1 Dec 2014 12:59:56 -0300 Subject: [PATCH 17/20] fix tests with new script api --- lib/script.js | 10 ++++++++-- test/script.js | 5 ++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/script.js b/lib/script.js index 8260ff6..94cb9c8 100644 --- a/lib/script.js +++ b/lib/script.js @@ -191,9 +191,15 @@ Script.prototype.isPublicKeyHashOut = function() { Script.prototype.isPublicKeyHashIn = function() { return !!(this.chunks.length === 2 && this.chunks[0].buf && - this.chunks[0].buf.length === 0x47 && + this.chunks[0].buf.length >= 0x47 && + this.chunks[0].buf.length <= 0x49 && this.chunks[1].buf && - this.chunks[1].buf.length === 0x21); + ( + // compressed public key + (this.chunks[1].buf[0] === 0x03 && this.chunks[1].buf.length === 0x21) || + // uncompressed public key + (this.chunks[1].buf[0] === 0x04 && this.chunks[1].buf.length === 0x41)) + ); }; /** diff --git a/test/script.js b/test/script.js index 5cec12a..0b04340 100644 --- a/test/script.js +++ b/test/script.js @@ -229,7 +229,7 @@ describe('Script', function() { describe('#isScripthashIn', function() { it('should identify this known scripthashin', function() { - Script('20 0000000000000000000000000000000000000000').isScriptHashIn().should.equal(true); + Script('OP_0 73 0x30460221008ca148504190c10eea7f5f9c283c719a37be58c3ad617928011a1bb9570901d2022100ced371a23e86af6f55ff4ce705c57d2721a09c4d192ca39d82c4239825f75a9801 72 0x30450220357011fd3b3ad2b8f2f2d01e05dc6108b51d2a245b4ef40c112d6004596f0475022100a8208c93a39e0c366b983f9a80bfaf89237fcd64ca543568badd2d18ee2e1d7501 OP_PUSHDATA1 105 0x5221024c02dff2f0b8263a562a69ec875b2c95ffad860f428acf2f9e8c6492bd067d362103546324a1351a6b601c623b463e33b6103ca444707d5b278ece1692f1aa7724a42103b1ad3b328429450069cc3f9fa80d537ee66ba1120e93f3f185a5bf686fb51e0a53ae').isScriptHashIn().should.equal(true); }); it('should identify this known non-scripthashin', function() { @@ -296,8 +296,7 @@ describe('Script', function() { Script('OP_HASH160 20 0x0000000000000000000000000000000000000000 OP_EQUAL').classify().should.equal(Script.types.SCRIPTHASH_OUT); }); it('should classify script hash in', function() { - var x = Script('OP_0 73 0x30460221008ca148504190c10eea7f5f9c283c719a37be58c3ad617928011a1bb9570901d2022100ced371a23e86af6f55ff4ce705c57d2721a09c4d192ca39d82c4239825f75a9801 72 0x30450220357011fd3b3ad2b8f2f2d01e05dc6108b51d2a245b4ef40c112d6004596f0475022100a8208c93a39e0c366b983f9a80bfaf89237fcd64ca543568badd2d18ee2e1d7501 OP_PUSHDATA1 105 0x5221024c02dff2f0b8263a562a69ec875b2c95ffad860f428acf2f9e8c6492bd067d362103546324a1351a6b601c623b463e33b6103ca444707d5b278ece1692f1aa7724a42103b1ad3b328429450069cc3f9fa80d537ee66ba1120e93f3f185a5bf686fb51e0a53ae'); - x.classify().should.equal(Script.types.SCRIPTHASH_IN); + Script('OP_0 73 0x30460221008ca148504190c10eea7f5f9c283c719a37be58c3ad617928011a1bb9570901d2022100ced371a23e86af6f55ff4ce705c57d2721a09c4d192ca39d82c4239825f75a9801 72 0x30450220357011fd3b3ad2b8f2f2d01e05dc6108b51d2a245b4ef40c112d6004596f0475022100a8208c93a39e0c366b983f9a80bfaf89237fcd64ca543568badd2d18ee2e1d7501 OP_PUSHDATA1 105 0x5221024c02dff2f0b8263a562a69ec875b2c95ffad860f428acf2f9e8c6492bd067d362103546324a1351a6b601c623b463e33b6103ca444707d5b278ece1692f1aa7724a42103b1ad3b328429450069cc3f9fa80d537ee66ba1120e93f3f185a5bf686fb51e0a53ae').classify().should.equal(Script.types.SCRIPTHASH_IN); }); it('should classify MULTISIG out', function() { Script('OP_2 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 OP_2 OP_CHECKMULTISIG').classify().should.equal(Script.types.MULTISIG_OUT); From 3bc00a55dbffa4726304c7118cdec9e90f6a953d Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Mon, 1 Dec 2014 13:28:44 -0300 Subject: [PATCH 18/20] remove .jsbeautifyrc --- .jsbeautifyrc | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 .jsbeautifyrc diff --git a/.jsbeautifyrc b/.jsbeautifyrc deleted file mode 100644 index 79f9b77..0000000 --- a/.jsbeautifyrc +++ /dev/null @@ -1,20 +0,0 @@ -{ - "indent_size": 2, - "indent_char": " ", - "indent_level": 0, - "indent_with_tabs": false, - "preserve_newlines": true, - "max_preserve_newlines": 10, - "jslint_happy": false, - "space_after_anon_function": true, - "brace_style": "collapse", - "keep_array_indentation": false, - "keep_function_indentation": false, - "space_before_conditional": true, - "break_chained_methods": false, - "eval_code": false, - "unescape_strings": false, - "wrap_line_length": 0, - "good-stuff": true - -} From 883a7cae5466aa15cbe7b4d642de22e240e77eac Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Mon, 1 Dec 2014 13:31:06 -0300 Subject: [PATCH 19/20] add unkown script test --- test/script.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/script.js b/test/script.js index 0b04340..d61e53a 100644 --- a/test/script.js +++ b/test/script.js @@ -313,6 +313,9 @@ describe('Script', function() { it('should classify public key in', function() { Script('47 0x3044022007415aa37ce7eaa6146001ac8bdefca0ddcba0e37c5dc08c4ac99392124ebac802207d382307fd53f65778b07b9c63b6e196edeadf0be719130c5db21ff1e700d67501').classify().should.equal(Script.types.PUBKEY_IN); }); + it('should classify unknown', function() { + Script('OP_TRUE OP_FALSE').classify().should.equal(Script.types.UNKNOWN); + }); }); describe('#add and #prepend', function() { From ef8f1eabd259781d84fe1dbf031d77a16b804bbf Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Tue, 2 Dec 2014 14:19:56 -0300 Subject: [PATCH 20/20] add script.isStandard() --- lib/script.js | 9 +++++++++ test/script.js | 14 ++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/lib/script.js b/lib/script.js index 94cb9c8..2474a5b 100644 --- a/lib/script.js +++ b/lib/script.js @@ -329,6 +329,15 @@ Script.prototype.classify = function() { return Script.types.UNKNOWN; }; + +/** + * @returns true if script is one of the known types + */ +Script.prototype.isStandard = function() { + return this.classify() !== Script.types.UNKNOWN; +}; + + // Script construction methods /** diff --git a/test/script.js b/test/script.js index d61e53a..ce0d896 100644 --- a/test/script.js +++ b/test/script.js @@ -166,6 +166,11 @@ describe('Script', function() { describe('#toString', function() { + it('should work with an empty script', function() { + var script = new Script(); + script.toString().should.equal(''); + }); + it('should output this buffer an OP code, data, and another OP code', function() { var buf = new Buffer([0, 0, 0, 0, 0, 0, 1, 2, 3, 0]); buf[0] = Opcode('OP_0').toNumber(); @@ -361,4 +366,13 @@ describe('Script', function() { }); }); + describe('#isStandard', function() { + it('should classify correctly standard script', function() { + Script('OP_RETURN 1 0x00').isStandard().should.equal(true); + }); + it('should classify correctly non standard script', function() { + Script('OP_TRUE OP_FALSE').isStandard().should.equal(false); + }); + }); + });