From ef26931645ffb32b4f48605e89638e4f8ff20d2a Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Wed, 8 Jul 2015 15:11:06 -0400 Subject: [PATCH 1/5] Use late definition to resolve circular dependency. --- lib/address.js | 3 ++- lib/script/script.js | 10 +--------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/lib/address.js b/lib/address.js index f49acd7..4905ed1 100644 --- a/lib/address.js +++ b/lib/address.js @@ -7,7 +7,6 @@ var Base58Check = require('./encoding/base58check'); var Networks = require('./networks'); var Hash = require('./crypto/hash'); var JSUtil = require('./util/js'); -var Script = require('./script'); var PublicKey = require('./publickey'); /** @@ -505,3 +504,5 @@ Address.prototype.inspect = function() { }; module.exports = Address; + +var Script = require('./script'); diff --git a/lib/script/script.js b/lib/script/script.js index 21fe7fb..c2f9bad 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -1,6 +1,6 @@ 'use strict'; - +var Address = require('../address'); var BufferReader = require('../encoding/bufferreader'); var BufferWriter = require('../encoding/bufferwriter'); var Hash = require('../crypto/hash'); @@ -8,7 +8,6 @@ var Opcode = require('../opcode'); var PublicKey = require('../publickey'); var Signature = require('../crypto/signature'); var Networks = require('../networks'); - var $ = require('../util/preconditions'); var _ = require('lodash'); var errors = require('../errors'); @@ -29,8 +28,6 @@ var Script = function Script(from) { if (!(this instanceof Script)) { return new Script(from); } - var Address = require('../address'); - this.chunks = []; if (BufferUtil.isBuffer(from)) { @@ -639,7 +636,6 @@ Script.buildP2SHMultisigIn = function(pubkeys, threshold, signatures, opts) { * @param {(Address|PublicKey)} to - destination address or public key */ Script.buildPublicKeyHashOut = function(to) { - var Address = require('../address'); $.checkArgument(!_.isUndefined(to)); $.checkArgument(to instanceof PublicKey || to instanceof Address || _.isString(to)); if (to instanceof PublicKey) { @@ -692,7 +688,6 @@ Script.buildDataOut = function(data) { * @returns {Script} new pay to script hash script for given script */ Script.buildScriptHashOut = function(script) { - var Address = require('../address'); $.checkArgument(script instanceof Script || (script instanceof Address && script.isPayToScriptHash())); var s = new Script(); @@ -745,7 +740,6 @@ Script.prototype.toScriptHashOut = function() { * @return {Script} an output script built from the address */ Script.fromAddress = function(address) { - var Address = require('../address'); address = Address(address); if (address.isPayToScriptHash()) { return Script.buildScriptHashOut(address); @@ -761,7 +755,6 @@ Script.fromAddress = function(address) { * for this script if any, or false */ Script.prototype.getAddressInfo = function() { - var Address = require('../address'); var info = {}; if (this.isScriptHashOut()) { info.hashBuffer = this.getData(); @@ -787,7 +780,6 @@ Script.prototype.getAddressInfo = function() { * @return {Address|boolean} the associated address for this script if possible, or false */ Script.prototype.toAddress = function(network) { - var Address = require('../address'); network = Networks.get(network) || this._network || Networks.defaultNetwork; var info = this.getAddressInfo(); if (!info) { From e8446d45376c8a444de2815ccc5cc90cb38bba54 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Wed, 8 Jul 2015 15:29:54 -0400 Subject: [PATCH 2/5] Removed double network call --- lib/script/script.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/script/script.js b/lib/script/script.js index c2f9bad..6bffeb0 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -780,12 +780,11 @@ Script.prototype.getAddressInfo = function() { * @return {Address|boolean} the associated address for this script if possible, or false */ Script.prototype.toAddress = function(network) { - network = Networks.get(network) || this._network || Networks.defaultNetwork; var info = this.getAddressInfo(); if (!info) { return false; } - info.network = Networks.get(network) || Networks.defaultNetwork; + info.network = Networks.get(network) || this._network || Networks.defaultNetwork; return new Address(info); }; From 6d86c99314116a6c4b97d5977563d4cc209ffd97 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Wed, 8 Jul 2015 16:43:49 -0400 Subject: [PATCH 3/5] Seperate getAddressInfo for input or output only use. --- benchmark/script.js | 28 ++++++++++++++++++ lib/script/script.js | 67 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 84 insertions(+), 11 deletions(-) diff --git a/benchmark/script.js b/benchmark/script.js index 33229dc..4b59c83 100644 --- a/benchmark/script.js +++ b/benchmark/script.js @@ -15,6 +15,8 @@ async.series([ var c = 0; var scripts = []; + var inputScripts = []; + var outputScripts = []; var block = bitcore.Block.fromString(blockData); for (var i = 0; i < block.transactions.length; i++) { var tx = block.transactions[i]; @@ -22,6 +24,14 @@ async.series([ var input = tx.inputs[j]; if (input.script) { scripts.push(input.script); + inputScripts.push(input.script); + } + } + for (var k = 0; k < tx.outputs.length; k++) { + var output = tx.outputs[k]; + if (output.script) { + scripts.push(output.script); + outputScripts.push(output.script); } } } @@ -42,6 +52,22 @@ async.series([ c++; } + function toOutputAddress() { + if (c >= outputScripts.length) { + c = 0; + } + outputScripts[c].toOutputAddress(); + c++; + } + + function toInputAddress() { + if (c >= inputScripts.length) { + c = 0; + } + inputScripts[c].toInputAddress(); + c++; + } + function getAddressInfo() { if (c >= scripts.length) { c = 0; @@ -53,6 +79,8 @@ async.series([ var suite = new benchmark.Suite(); suite.add('isPublicKeyHashIn', isPublicKeyHashIn, {maxTime: maxTime}); suite.add('toAddress', toAddress, {maxTime: maxTime}); + suite.add('toInputAddress', toInputAddress, {maxTime: maxTime}); + suite.add('toOutputAddress', toOutputAddress, {maxTime: maxTime}); suite.add('getAddressInfo', getAddressInfo, {maxTime: maxTime}); suite .on('cycle', function(event) { diff --git a/lib/script/script.js b/lib/script/script.js index 6bffeb0..0856245 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -750,11 +750,22 @@ Script.fromAddress = function(address) { }; /** - * @param {Network=} network - * @return {Address|boolean} the associated address information object - * for this script if any, or false + * Will return the associated address information object + * @return {Address|boolean} */ -Script.prototype.getAddressInfo = function() { +Script.prototype.getAddressInfo = function(opts) { + var info = this.getOutputAddressInfo(); + if (!info) { + return this.getInputAddressInfo(); + } + return info; +}; + +/** + * Will return the associated output scriptPubKey address information object + * @return {Address|boolean} + */ +Script.prototype.getOutputAddressInfo = function() { var info = {}; if (this.isScriptHashOut()) { info.hashBuffer = this.getData(); @@ -762,7 +773,19 @@ Script.prototype.getAddressInfo = function() { } else if (this.isPublicKeyHashOut()) { info.hashBuffer = this.getData(); info.type = Address.PayToPublicKeyHash; - } else if (this.isPublicKeyHashIn()) { + } else { + return false; + } + return info; +}; + +/** + * Will return the associated input scriptSig address information object + * @return {Address|boolean} + */ +Script.prototype.getInputAddressInfo = function() { + var info = {}; + if (this.isPublicKeyHashIn()) { // hash the publickey found in the scriptSig info.hashBuffer = Hash.sha256ripemd160(this.chunks[1].buf); info.type = Address.PayToPublicKeyHash; @@ -775,12 +798,8 @@ Script.prototype.getAddressInfo = function() { } return info; }; -/** - * @param {Network=} network - * @return {Address|boolean} the associated address for this script if possible, or false - */ -Script.prototype.toAddress = function(network) { - var info = this.getAddressInfo(); + +Script.prototype._toAddress = function(info, network) { if (!info) { return false; } @@ -788,6 +807,32 @@ Script.prototype.toAddress = function(network) { return new Address(info); }; +/** + * @param {Network=} network + * @return {Address|boolean} the associated address for this script if possible, or false + */ +Script.prototype.toAddress = function(network) { + return this._toAddress(this.getAddressInfo(), network); +}; + +/** + * Will get the associated address for an output scriptPubKey + * @param {Network=} network + * @return {Address|boolean} + */ +Script.prototype.toOutputAddress = function(network) { + return this._toAddress(this.getOutputAddressInfo(), network); +}; + +/** + * Will get the associated address for an input scriptSig + * @param {Network=} network + * @return {Address|boolean} + */ +Script.prototype.toInputAddress = function(network) { + return this._toAddress(this.getInputAddressInfo(), network); +}; + /** * Analagous to bitcoind's FindAndDelete. Find and delete equivalent chunks, * typically used with push data chunks. Note that this will find and delete From 55afeb3eaf56068edef1d7883a6c045ea0551174 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Wed, 8 Jul 2015 22:15:20 -0400 Subject: [PATCH 4/5] Use Node.js crypto ripemd160 hash if available. --- lib/crypto/hash.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/crypto/hash.js b/lib/crypto/hash.js index 0661a46..f6a625a 100644 --- a/lib/crypto/hash.js +++ b/lib/crypto/hash.js @@ -29,10 +29,19 @@ Hash.sha256sha256 = function(buf) { Hash.ripemd160 = function(buf) { $.checkArgument(BufferUtil.isBuffer(buf)); - var hash = (new hashjs.ripemd160()).update(buf).digest(); - return new Buffer(hash); + return crypto.createHash('ripemd160').update(buf).digest(); }; +// Node.js crypto ripemd160 hashes are not supported in a browser +// We'll replace with a (slower) version that does. +if (global.window) { + Hash.ripemd160 = function(buf) { + $.checkArgument(BufferUtil.isBuffer(buf)); + var hash = (new hashjs.ripemd160()).update(buf).digest(); + return new Buffer(hash); + }; +} + Hash.sha256ripemd160 = function(buf) { $.checkArgument(BufferUtil.isBuffer(buf)); return Hash.ripemd160(Hash.sha256(buf)); From d9047eebf08ae5bb4e1f5fd5027f7daa592758ee Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Thu, 9 Jul 2015 11:26:09 -0400 Subject: [PATCH 5/5] Detect if scriptSig (input) or scriptPubKey (output) is previously known. --- benchmark/script.js | 22 -------------- lib/script/script.js | 53 +++++++++++++-------------------- lib/transaction/input/input.js | 3 ++ lib/transaction/output.js | 3 ++ test/transaction/transaction.js | 4 ++- 5 files changed, 29 insertions(+), 56 deletions(-) diff --git a/benchmark/script.js b/benchmark/script.js index 4b59c83..7442d1a 100644 --- a/benchmark/script.js +++ b/benchmark/script.js @@ -15,8 +15,6 @@ async.series([ var c = 0; var scripts = []; - var inputScripts = []; - var outputScripts = []; var block = bitcore.Block.fromString(blockData); for (var i = 0; i < block.transactions.length; i++) { var tx = block.transactions[i]; @@ -24,14 +22,12 @@ async.series([ var input = tx.inputs[j]; if (input.script) { scripts.push(input.script); - inputScripts.push(input.script); } } for (var k = 0; k < tx.outputs.length; k++) { var output = tx.outputs[k]; if (output.script) { scripts.push(output.script); - outputScripts.push(output.script); } } } @@ -52,22 +48,6 @@ async.series([ c++; } - function toOutputAddress() { - if (c >= outputScripts.length) { - c = 0; - } - outputScripts[c].toOutputAddress(); - c++; - } - - function toInputAddress() { - if (c >= inputScripts.length) { - c = 0; - } - inputScripts[c].toInputAddress(); - c++; - } - function getAddressInfo() { if (c >= scripts.length) { c = 0; @@ -79,8 +59,6 @@ async.series([ var suite = new benchmark.Suite(); suite.add('isPublicKeyHashIn', isPublicKeyHashIn, {maxTime: maxTime}); suite.add('toAddress', toAddress, {maxTime: maxTime}); - suite.add('toInputAddress', toInputAddress, {maxTime: maxTime}); - suite.add('toOutputAddress', toOutputAddress, {maxTime: maxTime}); suite.add('getAddressInfo', getAddressInfo, {maxTime: maxTime}); suite .on('cycle', function(event) { diff --git a/lib/script/script.js b/lib/script/script.js index 0856245..5bcdce6 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -754,18 +754,25 @@ Script.fromAddress = function(address) { * @return {Address|boolean} */ Script.prototype.getAddressInfo = function(opts) { - var info = this.getOutputAddressInfo(); - if (!info) { - return this.getInputAddressInfo(); + if (this._isInput) { + return this._getInputAddressInfo(); + } else if (this._isOutput) { + return this._getOutputAddressInfo(); + } else { + var info = this._getOutputAddressInfo(); + if (!info) { + return this._getInputAddressInfo(); + } + return info; } - return info; }; /** * Will return the associated output scriptPubKey address information object * @return {Address|boolean} + * @private */ -Script.prototype.getOutputAddressInfo = function() { +Script.prototype._getOutputAddressInfo = function() { var info = {}; if (this.isScriptHashOut()) { info.hashBuffer = this.getData(); @@ -782,8 +789,9 @@ Script.prototype.getOutputAddressInfo = function() { /** * Will return the associated input scriptSig address information object * @return {Address|boolean} + * @private */ -Script.prototype.getInputAddressInfo = function() { +Script.prototype._getInputAddressInfo = function() { var info = {}; if (this.isPublicKeyHashIn()) { // hash the publickey found in the scriptSig @@ -799,38 +807,17 @@ Script.prototype.getInputAddressInfo = function() { return info; }; -Script.prototype._toAddress = function(info, network) { - if (!info) { - return false; - } - info.network = Networks.get(network) || this._network || Networks.defaultNetwork; - return new Address(info); -}; - /** * @param {Network=} network * @return {Address|boolean} the associated address for this script if possible, or false */ Script.prototype.toAddress = function(network) { - return this._toAddress(this.getAddressInfo(), network); -}; - -/** - * Will get the associated address for an output scriptPubKey - * @param {Network=} network - * @return {Address|boolean} - */ -Script.prototype.toOutputAddress = function(network) { - return this._toAddress(this.getOutputAddressInfo(), network); -}; - -/** - * Will get the associated address for an input scriptSig - * @param {Network=} network - * @return {Address|boolean} - */ -Script.prototype.toInputAddress = function(network) { - return this._toAddress(this.getInputAddressInfo(), network); + var info = this.getAddressInfo(); + if (!info) { + return false; + } + info.network = Networks.get(network) || this._network || Networks.defaultNetwork; + return new Address(info); }; /** diff --git a/lib/transaction/input/input.js b/lib/transaction/input/input.js index afa169e..d810890 100644 --- a/lib/transaction/input/input.js +++ b/lib/transaction/input/input.js @@ -34,6 +34,7 @@ Object.defineProperty(Input.prototype, 'script', { } if (!this._script) { this._script = new Script(this._scriptBuffer); + this._script._isInput = true; } return this._script; } @@ -116,6 +117,7 @@ Input.prototype.setScript = function(script) { this._script = null; if (script instanceof Script) { this._script = script; + this._script._isInput = true; this._scriptBuffer = script.toBuffer(); } else if (JSUtil.isHexa(script)) { // hex string script @@ -123,6 +125,7 @@ Input.prototype.setScript = function(script) { } else if (_.isString(script)) { // human readable string script this._script = new Script(script); + this._script._isInput = true; this._scriptBuffer = this._script.toBuffer(); } else if (BufferUtil.isBuffer(script)) { // buffer script diff --git a/lib/transaction/output.js b/lib/transaction/output.js index 684b7d2..62af6b4 100644 --- a/lib/transaction/output.js +++ b/lib/transaction/output.js @@ -113,6 +113,7 @@ Output.prototype.setScriptFromBuffer = function(buffer) { this._scriptBuffer = buffer; try { this._script = Script.fromBuffer(this._scriptBuffer); + this._script._isOutput = true; } catch(e) { if (e instanceof errors.Script.InvalidBuffer) { this._script = null; @@ -126,9 +127,11 @@ Output.prototype.setScript = function(script) { if (script instanceof Script) { this._scriptBuffer = script.toBuffer(); this._script = script; + this._script._isOutput = true; } else if (_.isString(script)) { this._script = Script.fromString(script); this._scriptBuffer = this._script.toBuffer(); + this._script._isOutput = true; } else if (bufferUtil.isBuffer(script)) { this.setScriptFromBuffer(script); } else { diff --git a/test/transaction/transaction.js b/test/transaction/transaction.js index b828ec5..8980969 100644 --- a/test/transaction/transaction.js +++ b/test/transaction/transaction.js @@ -202,7 +202,9 @@ describe('Transaction', function() { transaction.outputs[1].satoshis.should.equal(40000); transaction.outputs[1].script.toString() .should.equal(Script.fromAddress(changeAddress).toString()); - transaction.getChangeOutput().script.should.deep.equal(Script.fromAddress(changeAddress)); + var actual = transaction.getChangeOutput().script.toString(); + var expected = Script.fromAddress(changeAddress).toString(); + actual.should.equal(expected); }); it('accepts a P2SH address for change', function() { var transaction = new Transaction()