From d05aa27bed35c0dc68805001be7262899a0ed2f5 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Wed, 3 Dec 2014 20:40:06 -0300 Subject: [PATCH 01/11] finish all tests --- lib/script.js | 55 +++++++++++++++++++++++++++++++ test/script.js | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+) diff --git a/lib/script.js b/lib/script.js index 2474a5bbf..7b78e9429 100644 --- a/lib/script.js +++ b/lib/script.js @@ -421,4 +421,59 @@ Script.prototype._addBuffer = function(buf, prepend) { return this; }; + +// high level script builder methods + +/** + * @returns a new Multisig output script for given public keys, + * requiring m of those public keys to spend + */ +Script.buildMultisigOut = function(pubkeys, m) { + console.log(pubkeys); + console.log(m); + return new Script(); +}; + +/** + * @returns a new pay to public key hash output for the given + * address or public key + */ +Script.buildPublicKeyHashOut = function(to) { + console.log(to); + return new Script(); +}; + +/** + * @returns a new pay to public key output for the given + * public key + */ +Script.buildPublicKeyOut = function(pubkey) { + console.log(pubkey); + return new Script(); +}; + +/** + * @returns a new OP_RETURN script with data + */ +Script.buildDataOut = function(data) { + console.log(data); + return new Script(); +}; + +/** + * @returns a new pay to script hash script for given script + */ +Script.buildScriptHashOut = function(script) { + console.log(script); + return new Script(); +}; + + +/** + * @returns a new pay to script hash script that pays to this script + */ +Script.prototype.toScriptHashOut = function() { + return Script.buildScriptHashOut(this); +}; + module.exports = Script; diff --git a/test/script.js b/test/script.js index ce0d8963a..7c27ba54e 100644 --- a/test/script.js +++ b/test/script.js @@ -4,6 +4,8 @@ var should = require('chai').should(); var bitcore = require('..'); var Script = bitcore.Script; var Opcode = bitcore.Opcode; +var PublicKey = bitcore.PublicKey; +var Address = bitcore.Address; describe('Script', function() { @@ -374,5 +376,91 @@ describe('Script', function() { Script('OP_TRUE OP_FALSE').isStandard().should.equal(false); }); }); + describe('new methods', function() { + describe('#buildMultisigOut', function() { + var pubkey_hexs = [ + '022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da', + '03e3818b65bcc73a7d64064106a859cc1a5a728c4345ff0b641209fba0d90de6e9', + '021f2f6e1e50cb6a953935c3601284925decd3fd21bc445712576873fb8c6ebc18', + '02bf97f572a02a8900246d72c2e8fa3d3798a6e59c4e17de2d131d9c60d0d9b574', + '036a98a36aa7665874b1ba9130bc6d318e52fd3bdb5969532d7fc09bf2476ff842', + '033aafcbead78c08b0e0aacc1b0cdb40702a7c709b660bebd286e973242127e15b', + ]; + var test_mn = function(m, n) { + var pubkeys = pubkey_hexs.slice(0, n).map(PublicKey); + var s = Script.buildMultisigOut(pubkeys, m); + should.exist(s); + s.isMultisigOut().should.equal(true); + }; + for (var n = 1; n < 6; n++) { + for (var m = 1; m <= n; m++) { + it('should create ' + m + '-of-' + n, test_mn.bind(null, m, n)); + } + } + }); + describe('#buildPublicKeyHashOut', function() { + it('should create script from livenet address', function() { + var address = Address.fromString('1NaTVwXDDUJaXDQajoa9MqHhz4uTxtgK14'); + var s = Script.buildPublicKeyHashOut(address); + should.exist(s); + s.toString().should.equal('OP_DUP OP_HASH160 ecae7d092947b7ee4998e254aa48900d26d2ce1d OP_EQUALVERIFY OP_CHECKSIG'); + }); + it('should create script from testnet address', function() { + var address = Address.fromString('mxRN6AQJaDi5R6KmvMaEmZGe3n5ScV9u33'); + var s = Script.buildPublicKeyHashOut(address); + should.exist(s); + s.toString().should.equal('OP_DUP OP_HASH160 b96b816f378babb1fe585b7be7a2cd16eb99b3e4 OP_EQUALVERIFY OP_CHECKSIG'); + }); + it('should create script from public key', function() { + var pubkey = new PublicKey('022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da'); + var s = Script.buildPublicKeyHashOut(pubkey); + should.exist(s); + s.toString().should.equal('OP_DUP OP_HASH160 9674af7395592ec5d91573aa8d6557de55f60147 OP_EQUALVERIFY OP_CHECKSIG'); + }); + }); + describe('#buildPublicKeyOut', function() { + it('should create script from public key', function() { + var pubkey = new PublicKey('022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da'); + var s = Script.buildPublicKeyOut(pubkey); + should.exist(s); + s.toString().should.equal('9674af7395592ec5d91573aa8d6557de55f60147 OP_CHECKSIG'); + }); + }); + describe('#buildDataOut', function() { + it('should create script from empty data', function() { + var data = new Buffer(); + var s = Script.buildDataOut(data); + should.exist(s); + s.toString().should.equal('OP_RETURN'); + }); + it('should create script from some data', function() { + var data = new Buffer('bacacafe0102030405', 'hex'); + var s = Script.buildDataOut(data); + should.exist(s); + s.toString().should.equal('OP_RETURN bacacafe0102030405'); + }); + it('should create script from string', function() { + var data = 'hello world'; + var s = Script.buildDataOut(data); + should.exist(s); + s.toString().should.equal('OP_RETURN 68656c6c6f20776f726c64212121'); + }); + }); + describe('#buildScriptHashOut', function() { + it('should create script from another script', function() { + var inner = new Script('OP_DUP OP_HASH160 06c06f6d931d7bfba2b5bd5ad0d19a8f257af3e3 OP_EQUALVERIFY OP_CHECKSIG'); + var s = Script.buildScriptHashOut(inner); + should.exist(s); + s.toString().should.equal('OP_HASH160 b96d20131e15d948aa9196e477e9611d8e43c8f0 OP_EQUAL'); + }); + }); + describe('#toScriptHashOut', function() { + it('should create script from another script', function() { + var s = new Script('OP_DUP OP_HASH160 06c06f6d931d7bfba2b5bd5ad0d19a8f257af3e3 OP_EQUALVERIFY OP_CHECKSIG'); + s.toScriptHashOut().toString().should.equal('OP_HASH160 b96d20131e15d948aa9196e477e9611d8e43c8f0 OP_EQUAL'); + }); + }); + + }); }); From 4fae69807d87d8b73426d762cb1bc8b05614efd1 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Thu, 4 Dec 2014 10:33:09 -0300 Subject: [PATCH 02/11] change isOpReturn to isDataOut --- lib/script.js | 6 +++--- test/script.js | 24 +++++++++++++++++------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/script.js b/lib/script.js index 7b78e9429..036750916 100644 --- a/lib/script.js +++ b/lib/script.js @@ -283,7 +283,7 @@ Script.prototype.isMultisigIn = function() { /** * @returns true if this is an OP_RETURN data script */ -Script.prototype.isOpReturn = function() { +Script.prototype.isDataOut = function() { return (this.chunks[0] === Opcode('OP_RETURN').toNumber() && (this.chunks.length === 1 || (this.chunks.length === 2 && @@ -303,7 +303,7 @@ 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.types.DATA_OUT = 'Data push'; Script.identifiers = {}; Script.identifiers.PUBKEY_OUT = Script.prototype.isPublicKeyOut; @@ -312,9 +312,9 @@ Script.identifiers.PUBKEYHASH_OUT = Script.prototype.isPublicKeyHashOut; Script.identifiers.PUBKEYHASH_IN = Script.prototype.isPublicKeyHashIn; 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; +Script.identifiers.DATA_OUT = Script.prototype.isDataOut; /** * @returns {object} The Script type if it is a known form, diff --git a/test/script.js b/test/script.js index 7c27ba54e..b84a6ec8d 100644 --- a/test/script.js +++ b/test/script.js @@ -189,22 +189,22 @@ describe('Script', function() { }); - describe('#isOpReturn', function() { + describe('#isDataOut', function() { it('should know this is a (blank) OP_RETURN script', function() { - Script('OP_RETURN').isOpReturn().should.equal(true); + Script('OP_RETURN').isDataOut().should.equal(true); }); it('should know this is an OP_RETURN script', function() { var buf = new Buffer(40); buf.fill(0); - Script('OP_RETURN 40 0x' + buf.toString('hex')).isOpReturn().should.equal(true); + Script('OP_RETURN 40 0x' + buf.toString('hex')).isDataOut().should.equal(true); }); it('should know this is not an OP_RETURN script', function() { var buf = new Buffer(40); buf.fill(0); - Script('OP_CHECKMULTISIG 40 0x' + buf.toString('hex')).isOpReturn().should.equal(false); + Script('OP_CHECKMULTISIG 40 0x' + buf.toString('hex')).isDataOut().should.equal(false); }); }); @@ -311,8 +311,8 @@ describe('Script', function() { it('should classify MULTISIG in', function() { 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 OP_RETURN data out', function() { + Script('OP_RETURN 1 0x01').classify().should.equal(Script.types.DATA_OUT); }); it('should classify public key out', function() { Script('41 0x0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 OP_CHECKSIG').classify().should.equal(Script.types.PUBKEY_OUT); @@ -376,7 +376,8 @@ describe('Script', function() { Script('OP_TRUE OP_FALSE').isStandard().should.equal(false); }); }); - describe('new methods', function() { + + describe.only('new methods', function() { describe('#buildMultisigOut', function() { var pubkey_hexs = [ '022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da', @@ -404,18 +405,21 @@ describe('Script', function() { var s = Script.buildPublicKeyHashOut(address); should.exist(s); s.toString().should.equal('OP_DUP OP_HASH160 ecae7d092947b7ee4998e254aa48900d26d2ce1d OP_EQUALVERIFY OP_CHECKSIG'); + s.isPublicKeyHashOut().should.equal(true); }); it('should create script from testnet address', function() { var address = Address.fromString('mxRN6AQJaDi5R6KmvMaEmZGe3n5ScV9u33'); var s = Script.buildPublicKeyHashOut(address); should.exist(s); s.toString().should.equal('OP_DUP OP_HASH160 b96b816f378babb1fe585b7be7a2cd16eb99b3e4 OP_EQUALVERIFY OP_CHECKSIG'); + s.isPublicKeyHashOut().should.equal(true); }); it('should create script from public key', function() { var pubkey = new PublicKey('022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da'); var s = Script.buildPublicKeyHashOut(pubkey); should.exist(s); s.toString().should.equal('OP_DUP OP_HASH160 9674af7395592ec5d91573aa8d6557de55f60147 OP_EQUALVERIFY OP_CHECKSIG'); + s.isPublicKeyHashOut().should.equal(true); }); }); describe('#buildPublicKeyOut', function() { @@ -424,6 +428,7 @@ describe('Script', function() { var s = Script.buildPublicKeyOut(pubkey); should.exist(s); s.toString().should.equal('9674af7395592ec5d91573aa8d6557de55f60147 OP_CHECKSIG'); + s.isPublicKeyOut().should.equal(true); }); }); describe('#buildDataOut', function() { @@ -432,18 +437,21 @@ describe('Script', function() { var s = Script.buildDataOut(data); should.exist(s); s.toString().should.equal('OP_RETURN'); + s.isDataOut().should.equal(true); }); it('should create script from some data', function() { var data = new Buffer('bacacafe0102030405', 'hex'); var s = Script.buildDataOut(data); should.exist(s); s.toString().should.equal('OP_RETURN bacacafe0102030405'); + s.isDataOut().should.equal(true); }); it('should create script from string', function() { var data = 'hello world'; var s = Script.buildDataOut(data); should.exist(s); s.toString().should.equal('OP_RETURN 68656c6c6f20776f726c64212121'); + s.isDataOut().should.equal(true); }); }); describe('#buildScriptHashOut', function() { @@ -452,12 +460,14 @@ describe('Script', function() { var s = Script.buildScriptHashOut(inner); should.exist(s); s.toString().should.equal('OP_HASH160 b96d20131e15d948aa9196e477e9611d8e43c8f0 OP_EQUAL'); + s.isScriptHashOut().should.equal(true); }); }); describe('#toScriptHashOut', function() { it('should create script from another script', function() { var s = new Script('OP_DUP OP_HASH160 06c06f6d931d7bfba2b5bd5ad0d19a8f257af3e3 OP_EQUALVERIFY OP_CHECKSIG'); s.toScriptHashOut().toString().should.equal('OP_HASH160 b96d20131e15d948aa9196e477e9611d8e43c8f0 OP_EQUAL'); + s.isScriptHashOut().should.equal(true); }); }); From 7b54a534144ea278e477637d4db30326b6be8e37 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Thu, 4 Dec 2014 10:45:10 -0300 Subject: [PATCH 03/11] add Opcode.smallInt() --- lib/opcode.js | 10 ++++++++++ test/opcode.js | 17 +++++++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/lib/opcode.js b/lib/opcode.js index ccd653521..75e812006 100644 --- a/lib/opcode.js +++ b/lib/opcode.js @@ -46,6 +46,16 @@ Opcode.prototype.toString = function() { return str; }; +Opcode.smallInt = function(n) { + if (!(n >= 0 && n <= 16)) { + throw new Error('Invalid Argument: n must be between 0 and 16'); + } + if (n === 0) { + return Opcode('OP_0'); + } + return new Opcode(Opcode.map.OP_1 + n - 1); +}; + Opcode.map = { // push value OP_FALSE: 0, diff --git a/test/opcode.js b/test/opcode.js index c9d8307f8..41ccc4379 100644 --- a/test/opcode.js +++ b/test/opcode.js @@ -92,13 +92,22 @@ describe('Opcode', function() { Opcode('OP_16') ]; - describe('@isSmallIntOp', function() { - var testSmallInt = function() { - Opcode.isSmallIntOp(this).should.equal(true); + describe('@smallInt', function() { + var testSmallInt = function(n, op) { + Opcode.smallInt(n).toString().should.equal(op.toString()); }; 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 small int ' + op, testSmallInt.bind(null, i, op)); + } + }); + describe('@isSmallIntOp', function() { + var testIsSmallInt = function(op) { + Opcode.isSmallIntOp(op).should.equal(true); + }; + for (var i = 0; i < smallints.length; i++) { + var op = smallints[i]; + it('should work for small int ' + op, testIsSmallInt.bind(null, op)); } it('should work for non-small ints', function() { From 163925c75435cfb67d247800b2aa7bf7ed53754a Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Thu, 4 Dec 2014 10:49:38 -0300 Subject: [PATCH 04/11] implement Script.buildMultisigOut() --- lib/script.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/script.js b/lib/script.js index 036750916..8f7a15ccc 100644 --- a/lib/script.js +++ b/lib/script.js @@ -429,9 +429,15 @@ Script.prototype._addBuffer = function(buf, prepend) { * requiring m of those public keys to spend */ Script.buildMultisigOut = function(pubkeys, m) { - console.log(pubkeys); - console.log(m); - return new Script(); + var s = new Script(); + s.add(Opcode.smallInt(m)); + for (var i = 0; i Date: Thu, 4 Dec 2014 11:01:23 -0300 Subject: [PATCH 05/11] implement Script.buildPublicKeyHashOut() --- lib/script.js | 14 ++++++++++++-- test/script.js | 6 +++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/script.js b/lib/script.js index 8f7a15ccc..65fcb5555 100644 --- a/lib/script.js +++ b/lib/script.js @@ -3,6 +3,8 @@ var BufferReader = require('./encoding/bufferreader'); var BufferWriter = require('./encoding/bufferwriter'); var Opcode = require('./opcode'); +var Address = require('./address'); +var PublicKey = require('./publickey'); var Script = function Script(from) { if (!(this instanceof Script)) { @@ -445,8 +447,16 @@ Script.buildMultisigOut = function(pubkeys, m) { * address or public key */ Script.buildPublicKeyHashOut = function(to) { - console.log(to); - return new Script(); + if (to instanceof PublicKey) { + to = to.toAddress(); + } + var s = new Script(); + s.add(Opcode('OP_DUP')) + .add(Opcode('OP_HASH160')) + .add(to.hashBuffer) + .add(Opcode('OP_EQUALVERIFY')) + .add(Opcode('OP_CHECKSIG')); + return s; }; /** diff --git a/test/script.js b/test/script.js index b84a6ec8d..4e8844efa 100644 --- a/test/script.js +++ b/test/script.js @@ -404,21 +404,21 @@ describe('Script', function() { var address = Address.fromString('1NaTVwXDDUJaXDQajoa9MqHhz4uTxtgK14'); var s = Script.buildPublicKeyHashOut(address); should.exist(s); - s.toString().should.equal('OP_DUP OP_HASH160 ecae7d092947b7ee4998e254aa48900d26d2ce1d OP_EQUALVERIFY OP_CHECKSIG'); + s.toString().should.equal('OP_DUP OP_HASH160 20 0xecae7d092947b7ee4998e254aa48900d26d2ce1d OP_EQUALVERIFY OP_CHECKSIG'); s.isPublicKeyHashOut().should.equal(true); }); it('should create script from testnet address', function() { var address = Address.fromString('mxRN6AQJaDi5R6KmvMaEmZGe3n5ScV9u33'); var s = Script.buildPublicKeyHashOut(address); should.exist(s); - s.toString().should.equal('OP_DUP OP_HASH160 b96b816f378babb1fe585b7be7a2cd16eb99b3e4 OP_EQUALVERIFY OP_CHECKSIG'); + s.toString().should.equal('OP_DUP OP_HASH160 20 0xb96b816f378babb1fe585b7be7a2cd16eb99b3e4 OP_EQUALVERIFY OP_CHECKSIG'); s.isPublicKeyHashOut().should.equal(true); }); it('should create script from public key', function() { var pubkey = new PublicKey('022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da'); var s = Script.buildPublicKeyHashOut(pubkey); should.exist(s); - s.toString().should.equal('OP_DUP OP_HASH160 9674af7395592ec5d91573aa8d6557de55f60147 OP_EQUALVERIFY OP_CHECKSIG'); + s.toString().should.equal('OP_DUP OP_HASH160 20 0x9674af7395592ec5d91573aa8d6557de55f60147 OP_EQUALVERIFY OP_CHECKSIG'); s.isPublicKeyHashOut().should.equal(true); }); }); From 4bca5316eaf78002d550ac6f3c6f84e74fc0e93e Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Thu, 4 Dec 2014 14:49:20 -0300 Subject: [PATCH 06/11] implement Script.buildDDataOut() --- lib/script.js | 39 +++++++++++++++++++++++++++------------ test/script.js | 15 +++++++++------ 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/lib/script.js b/lib/script.js index 65fcb5555..008c48739 100644 --- a/lib/script.js +++ b/lib/script.js @@ -196,21 +196,27 @@ Script.prototype.isPublicKeyHashIn = function() { this.chunks[0].buf.length >= 0x47 && this.chunks[0].buf.length <= 0x49 && this.chunks[1].buf && + PublicKey.isValid(this.chunks[1].buf)); + /* ( // compressed public key - (this.chunks[1].buf[0] === 0x03 && this.chunks[1].buf.length === 0x21) || + ( + (this.chunks[1].buf[0] === 0x02 || 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)) ); + */ }; /** * @returns true if this is a public key output script */ Script.prototype.isPublicKeyOut = function() { + console.log(this.toString()); return this.chunks.length === 2 && Buffer.isBuffer(this.chunks[0].buf) && - this.chunks[0].buf.length === 0x41 && + PublicKey.isValid(this.chunks[0].buf) && this.chunks[1] === Opcode('OP_CHECKSIG').toNumber(); }; @@ -404,13 +410,15 @@ Script.prototype._addOpcode = function(opcode, prepend) { Script.prototype._addBuffer = function(buf, prepend) { var opcodenum; var len = buf.length; - if (buf.length > 0 && buf.length < Opcode.map.OP_PUSHDATA1) { - opcodenum = buf.length; - } else if (buf.length < Math.pow(2, 8)) { + if (len === 0) { + return; + } else if (len > 0 && len < Opcode.map.OP_PUSHDATA1) { + opcodenum = len; + } else if (len < Math.pow(2, 8)) { opcodenum = Opcode.map.OP_PUSHDATA1; - } else if (buf.length < Math.pow(2, 16)) { + } else if (len < Math.pow(2, 16)) { opcodenum = Opcode.map.OP_PUSHDATA2; - } else if (buf.length < Math.pow(2, 32)) { + } else if (len < Math.pow(2, 32)) { opcodenum = Opcode.map.OP_PUSHDATA4; } else { throw new Error('You can\'t push that much data'); @@ -433,7 +441,7 @@ Script.prototype._addBuffer = function(buf, prepend) { Script.buildMultisigOut = function(pubkeys, m) { var s = new Script(); s.add(Opcode.smallInt(m)); - for (var i = 0; i Date: Thu, 4 Dec 2014 15:06:47 -0300 Subject: [PATCH 07/11] implement Script.buildScriptHashOut() --- lib/script.js | 9 ++- test/script.js | 173 ++++++++++++++++++++++++------------------------- 2 files changed, 92 insertions(+), 90 deletions(-) diff --git a/lib/script.js b/lib/script.js index 008c48739..ab0398ad9 100644 --- a/lib/script.js +++ b/lib/script.js @@ -5,6 +5,7 @@ var BufferWriter = require('./encoding/bufferwriter'); var Opcode = require('./opcode'); var Address = require('./address'); var PublicKey = require('./publickey'); +var Hash = require('./crypto/hash'); var Script = function Script(from) { if (!(this instanceof Script)) { @@ -213,7 +214,6 @@ Script.prototype.isPublicKeyHashIn = function() { * @returns true if this is a public key output script */ Script.prototype.isPublicKeyOut = function() { - console.log(this.toString()); return this.chunks.length === 2 && Buffer.isBuffer(this.chunks[0].buf) && PublicKey.isValid(this.chunks[0].buf) && @@ -495,8 +495,11 @@ Script.buildDataOut = function(data) { * @returns a new pay to script hash script for given script */ Script.buildScriptHashOut = function(script) { - console.log(script); - return new Script(); + var s = new Script(); + s.add(Opcode('OP_HASH160')) + .add(Hash.sha256ripemd160(script.toBuffer())) + .add(Opcode('OP_EQUAL')); + return s; }; diff --git a/test/script.js b/test/script.js index 7bcef4cef..9091505d7 100644 --- a/test/script.js +++ b/test/script.js @@ -380,100 +380,99 @@ describe('Script', function() { }); }); - describe('new methods', function() { - describe('#buildMultisigOut', function() { - var pubkey_hexs = [ - '022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da', - '03e3818b65bcc73a7d64064106a859cc1a5a728c4345ff0b641209fba0d90de6e9', - '021f2f6e1e50cb6a953935c3601284925decd3fd21bc445712576873fb8c6ebc18', - '02bf97f572a02a8900246d72c2e8fa3d3798a6e59c4e17de2d131d9c60d0d9b574', - '036a98a36aa7665874b1ba9130bc6d318e52fd3bdb5969532d7fc09bf2476ff842', - '033aafcbead78c08b0e0aacc1b0cdb40702a7c709b660bebd286e973242127e15b', - ]; - var test_mn = function(m, n) { - var pubkeys = pubkey_hexs.slice(0, n).map(PublicKey); - var s = Script.buildMultisigOut(pubkeys, m); - should.exist(s); - s.isMultisigOut().should.equal(true); - }; - for (var n = 1; n < 6; n++) { - for (var m = 1; m <= n; m++) { - it('should create ' + m + '-of-' + n, test_mn.bind(null, m, n)); - } + describe('#buildMultisigOut', function() { + var pubkey_hexs = [ + '022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da', + '03e3818b65bcc73a7d64064106a859cc1a5a728c4345ff0b641209fba0d90de6e9', + '021f2f6e1e50cb6a953935c3601284925decd3fd21bc445712576873fb8c6ebc18', + '02bf97f572a02a8900246d72c2e8fa3d3798a6e59c4e17de2d131d9c60d0d9b574', + '036a98a36aa7665874b1ba9130bc6d318e52fd3bdb5969532d7fc09bf2476ff842', + '033aafcbead78c08b0e0aacc1b0cdb40702a7c709b660bebd286e973242127e15b', + ]; + var test_mn = function(m, n) { + var pubkeys = pubkey_hexs.slice(0, n).map(PublicKey); + var s = Script.buildMultisigOut(pubkeys, m); + should.exist(s); + s.isMultisigOut().should.equal(true); + }; + for (var n = 1; n < 6; n++) { + for (var m = 1; m <= n; m++) { + it('should create ' + m + '-of-' + n, test_mn.bind(null, m, n)); } + } + }); + describe('#buildPublicKeyHashOut', function() { + it('should create script from livenet address', function() { + var address = Address.fromString('1NaTVwXDDUJaXDQajoa9MqHhz4uTxtgK14'); + var s = Script.buildPublicKeyHashOut(address); + should.exist(s); + s.toString().should.equal('OP_DUP OP_HASH160 20 0xecae7d092947b7ee4998e254aa48900d26d2ce1d OP_EQUALVERIFY OP_CHECKSIG'); + s.isPublicKeyHashOut().should.equal(true); }); - describe('#buildPublicKeyHashOut', function() { - it('should create script from livenet address', function() { - var address = Address.fromString('1NaTVwXDDUJaXDQajoa9MqHhz4uTxtgK14'); - var s = Script.buildPublicKeyHashOut(address); - should.exist(s); - s.toString().should.equal('OP_DUP OP_HASH160 20 0xecae7d092947b7ee4998e254aa48900d26d2ce1d OP_EQUALVERIFY OP_CHECKSIG'); - s.isPublicKeyHashOut().should.equal(true); - }); - it('should create script from testnet address', function() { - var address = Address.fromString('mxRN6AQJaDi5R6KmvMaEmZGe3n5ScV9u33'); - var s = Script.buildPublicKeyHashOut(address); - should.exist(s); - s.toString().should.equal('OP_DUP OP_HASH160 20 0xb96b816f378babb1fe585b7be7a2cd16eb99b3e4 OP_EQUALVERIFY OP_CHECKSIG'); - s.isPublicKeyHashOut().should.equal(true); - }); - it('should create script from public key', function() { - var pubkey = new PublicKey('022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da'); - var s = Script.buildPublicKeyHashOut(pubkey); - should.exist(s); - s.toString().should.equal('OP_DUP OP_HASH160 20 0x9674af7395592ec5d91573aa8d6557de55f60147 OP_EQUALVERIFY OP_CHECKSIG'); - s.isPublicKeyHashOut().should.equal(true); - }); + it('should create script from testnet address', function() { + var address = Address.fromString('mxRN6AQJaDi5R6KmvMaEmZGe3n5ScV9u33'); + var s = Script.buildPublicKeyHashOut(address); + should.exist(s); + s.toString().should.equal('OP_DUP OP_HASH160 20 0xb96b816f378babb1fe585b7be7a2cd16eb99b3e4 OP_EQUALVERIFY OP_CHECKSIG'); + s.isPublicKeyHashOut().should.equal(true); }); - describe('#buildPublicKeyOut', function() { - it('should create script from public key', function() { - var pubkey = new PublicKey('022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da'); - var s = Script.buildPublicKeyOut(pubkey); - should.exist(s); - s.toString().should.equal('33 0x022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da OP_CHECKSIG'); - s.isPublicKeyOut().should.equal(true); - }); + it('should create script from public key', function() { + var pubkey = new PublicKey('022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da'); + var s = Script.buildPublicKeyHashOut(pubkey); + should.exist(s); + s.toString().should.equal('OP_DUP OP_HASH160 20 0x9674af7395592ec5d91573aa8d6557de55f60147 OP_EQUALVERIFY OP_CHECKSIG'); + s.isPublicKeyHashOut().should.equal(true); }); - describe('#buildDataOut', function() { - it('should create script from empty data', function() { - var data = new Buffer(''); - var s = Script.buildDataOut(data); - should.exist(s); - s.toString().should.equal('OP_RETURN'); - s.isDataOut().should.equal(true); - }); - it('should create script from some data', function() { - var data = new Buffer('bacacafe0102030405', 'hex'); - var s = Script.buildDataOut(data); - should.exist(s); - s.toString().should.equal('OP_RETURN 9 0xbacacafe0102030405'); - s.isDataOut().should.equal(true); - }); - it('should create script from string', function() { - var data = 'hello world!!!'; - var s = Script.buildDataOut(data); - should.exist(s); - s.toString().should.equal('OP_RETURN 14 0x68656c6c6f20776f726c64212121'); - s.isDataOut().should.equal(true); - }); + }); + describe('#buildPublicKeyOut', function() { + it('should create script from public key', function() { + var pubkey = new PublicKey('022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da'); + var s = Script.buildPublicKeyOut(pubkey); + should.exist(s); + s.toString().should.equal('33 0x022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da OP_CHECKSIG'); + s.isPublicKeyOut().should.equal(true); }); - describe('#buildScriptHashOut', function() { - it('should create script from another script', function() { - var inner = new Script('OP_DUP OP_HASH160 06c06f6d931d7bfba2b5bd5ad0d19a8f257af3e3 OP_EQUALVERIFY OP_CHECKSIG'); - var s = Script.buildScriptHashOut(inner); - should.exist(s); - s.toString().should.equal('OP_HASH160 b96d20131e15d948aa9196e477e9611d8e43c8f0 OP_EQUAL'); - s.isScriptHashOut().should.equal(true); - }); + }); + describe('#buildDataOut', function() { + it('should create script from empty data', function() { + var data = new Buffer(''); + var s = Script.buildDataOut(data); + should.exist(s); + s.toString().should.equal('OP_RETURN'); + s.isDataOut().should.equal(true); }); - describe('#toScriptHashOut', function() { - it('should create script from another script', function() { - var s = new Script('OP_DUP OP_HASH160 06c06f6d931d7bfba2b5bd5ad0d19a8f257af3e3 OP_EQUALVERIFY OP_CHECKSIG'); - s.toScriptHashOut().toString().should.equal('OP_HASH160 b96d20131e15d948aa9196e477e9611d8e43c8f0 OP_EQUAL'); - s.isScriptHashOut().should.equal(true); - }); + it('should create script from some data', function() { + var data = new Buffer('bacacafe0102030405', 'hex'); + var s = Script.buildDataOut(data); + should.exist(s); + s.toString().should.equal('OP_RETURN 9 0xbacacafe0102030405'); + s.isDataOut().should.equal(true); + }); + it('should create script from string', function() { + var data = 'hello world!!!'; + var s = Script.buildDataOut(data); + should.exist(s); + s.toString().should.equal('OP_RETURN 14 0x68656c6c6f20776f726c64212121'); + s.isDataOut().should.equal(true); + }); + }); + describe('#buildScriptHashOut', function() { + it('should create script from another script', function() { + var inner = new Script('OP_DUP OP_HASH160 06c06f6d931d7bfba2b5bd5ad0d19a8f257af3e3 OP_EQUALVERIFY OP_CHECKSIG'); + var s = Script.buildScriptHashOut(inner); + should.exist(s); + s.toString().should.equal('OP_HASH160 20 0xb96d20131e15d948aa9196e477e9611d8e43c8f0 OP_EQUAL'); + s.isScriptHashOut().should.equal(true); + }); + }); + describe('#toScriptHashOut', function() { + it('should create script from another script', function() { + var s = new Script('OP_DUP OP_HASH160 06c06f6d931d7bfba2b5bd5ad0d19a8f257af3e3 OP_EQUALVERIFY OP_CHECKSIG'); + var sho = s.toScriptHashOut(); + sho.toString().should.equal('OP_HASH160 20 0xb96d20131e15d948aa9196e477e9611d8e43c8f0 OP_EQUAL'); + sho.isScriptHashOut().should.equal(true); }); - }); + }); From d19ad6f384e6404fd2cb26347b49118938e72802 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Thu, 4 Dec 2014 15:10:19 -0300 Subject: [PATCH 08/11] remove commented code --- lib/script.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lib/script.js b/lib/script.js index ab0398ad9..4281ac8c3 100644 --- a/lib/script.js +++ b/lib/script.js @@ -198,16 +198,6 @@ Script.prototype.isPublicKeyHashIn = function() { this.chunks[0].buf.length <= 0x49 && this.chunks[1].buf && PublicKey.isValid(this.chunks[1].buf)); - /* - ( - // compressed public key - ( - (this.chunks[1].buf[0] === 0x02 || 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)) - ); - */ }; /** From 6c5198b74305b143eadb66ad2a693bd6d88ee0c2 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Thu, 4 Dec 2014 15:38:46 -0300 Subject: [PATCH 09/11] fix a browser test --- lib/script.js | 14 +++++++------- test/script.js | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/script.js b/lib/script.js index 4281ac8c3..4c9f313e1 100644 --- a/lib/script.js +++ b/lib/script.js @@ -3,9 +3,9 @@ var BufferReader = require('./encoding/bufferreader'); var BufferWriter = require('./encoding/bufferwriter'); var Opcode = require('./opcode'); -var Address = require('./address'); var PublicKey = require('./publickey'); var Hash = require('./crypto/hash'); +var bu = require('./util/buffer'); var Script = function Script(from) { if (!(this instanceof Script)) { @@ -14,7 +14,7 @@ var Script = function Script(from) { this.chunks = []; - if (Buffer.isBuffer(from)) { + if (bu.isBuffer(from)) { return Script.fromBuffer(from); } else if (typeof from === 'string') { return Script.fromString(from); @@ -205,7 +205,7 @@ Script.prototype.isPublicKeyHashIn = function() { */ Script.prototype.isPublicKeyOut = function() { return this.chunks.length === 2 && - Buffer.isBuffer(this.chunks[0].buf) && + bu.isBuffer(this.chunks[0].buf) && PublicKey.isValid(this.chunks[0].buf) && this.chunks[1] === Opcode('OP_CHECKSIG').toNumber(); }; @@ -215,7 +215,7 @@ Script.prototype.isPublicKeyOut = function() { */ Script.prototype.isPublicKeyIn = function() { return this.chunks.length === 1 && - Buffer.isBuffer(this.chunks[0].buf) && + bu.isBuffer(this.chunks[0].buf) && this.chunks[0].buf.length === 0x47; }; @@ -259,7 +259,7 @@ 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); + return obj.buf && bu.isBuffer(obj.buf); }) && Opcode.isSmallIntOp(this.chunks[this.chunks.length - 2]) && this.chunks[this.chunks.length - 1] === Opcode.map.OP_CHECKMULTISIG); @@ -273,7 +273,7 @@ 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) && + bu.isBuffer(obj.buf) && obj.buf.length === 0x47; }); }; @@ -367,7 +367,7 @@ Script.prototype._addByType = function(obj, prepend) { this._addOpcode(obj, prepend); } else if (obj.constructor && obj.constructor.name && obj.constructor.name === 'Opcode') { this._addOpcode(obj, prepend); - } else if (Buffer.isBuffer(obj)) { + } else if (bu.isBuffer(obj)) { this._addBuffer(obj, prepend); } else if (typeof obj === 'object') { this._insertAtPosition(obj, prepend); diff --git a/test/script.js b/test/script.js index 9091505d7..791d3d887 100644 --- a/test/script.js +++ b/test/script.js @@ -458,10 +458,10 @@ describe('Script', function() { }); describe('#buildScriptHashOut', function() { it('should create script from another script', function() { - var inner = new Script('OP_DUP OP_HASH160 06c06f6d931d7bfba2b5bd5ad0d19a8f257af3e3 OP_EQUALVERIFY OP_CHECKSIG'); + var inner = new Script('OP_DUP OP_HASH160 20 0x06c06f6d931d7bfba2b5bd5ad0d19a8f257af3e3 OP_EQUALVERIFY OP_CHECKSIG'); var s = Script.buildScriptHashOut(inner); should.exist(s); - s.toString().should.equal('OP_HASH160 20 0xb96d20131e15d948aa9196e477e9611d8e43c8f0 OP_EQUAL'); + s.toString().should.equal('OP_HASH160 20 0x45ea3f9133e7b1cef30ba606f8433f993e41e159 OP_EQUAL'); s.isScriptHashOut().should.equal(true); }); }); From f231f8a054c45daf86a0fcef98805d5f415fab21 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Thu, 4 Dec 2014 15:40:27 -0300 Subject: [PATCH 10/11] fix a browser test 2 --- test/script.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/script.js b/test/script.js index 791d3d887..818da0fd1 100644 --- a/test/script.js +++ b/test/script.js @@ -467,9 +467,9 @@ describe('Script', function() { }); describe('#toScriptHashOut', function() { it('should create script from another script', function() { - var s = new Script('OP_DUP OP_HASH160 06c06f6d931d7bfba2b5bd5ad0d19a8f257af3e3 OP_EQUALVERIFY OP_CHECKSIG'); + var s = new Script('OP_DUP OP_HASH160 20 0x06c06f6d931d7bfba2b5bd5ad0d19a8f257af3e3 OP_EQUALVERIFY OP_CHECKSIG'); var sho = s.toScriptHashOut(); - sho.toString().should.equal('OP_HASH160 20 0xb96d20131e15d948aa9196e477e9611d8e43c8f0 OP_EQUAL'); + sho.toString().should.equal('OP_HASH160 20 0x45ea3f9133e7b1cef30ba606f8433f993e41e159 OP_EQUAL'); sho.isScriptHashOut().should.equal(true); }); }); From 58b43345c02cac561fbce117d86d9ed82cfc43b2 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Thu, 4 Dec 2014 18:39:13 -0300 Subject: [PATCH 11/11] add docs --- lib/script.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/script.js b/lib/script.js index 4c9f313e1..df418f2c3 100644 --- a/lib/script.js +++ b/lib/script.js @@ -7,6 +7,15 @@ var PublicKey = require('./publickey'); var Hash = require('./crypto/hash'); var bu = require('./util/buffer'); +/** + * A bitcoin transaction script. Each transaction's inputs and outputs + * has a script that is evaluated to validate it's spending. + * + * See https://en.bitcoin.it/wiki/Script + * + * @constructor + * @param {Object|string|Buffer} [from] optional data to populate script + */ var Script = function Script(from) { if (!(this instanceof Script)) { return new Script(from); @@ -427,6 +436,8 @@ Script.prototype._addBuffer = function(buf, prepend) { /** * @returns a new Multisig output script for given public keys, * requiring m of those public keys to spend + * @param {PublicKey[]} pubkeys - list of all public keys controlling the output + * @param {number} m - amount of required signatures to spend the output */ Script.buildMultisigOut = function(pubkeys, m) { var s = new Script(); @@ -443,6 +454,7 @@ Script.buildMultisigOut = function(pubkeys, m) { /** * @returns a new pay to public key hash output for the given * address or public key + * @param {(Address|PublicKey)} to - destination address or public key */ Script.buildPublicKeyHashOut = function(to) { if (to instanceof PublicKey) { @@ -470,6 +482,7 @@ Script.buildPublicKeyOut = function(pubkey) { /** * @returns a new OP_RETURN script with data + * @param {(string|Buffer)} to - the data to embed in the output */ Script.buildDataOut = function(data) { if (typeof data === 'string') { @@ -483,6 +496,7 @@ Script.buildDataOut = function(data) { /** * @returns a new pay to script hash script for given script + * @param {Script} script - the redeemScript for the new p2sh output */ Script.buildScriptHashOut = function(script) { var s = new Script();