From a6584ae821c79424910fbbc618a215be7879c015 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 30 Nov 2015 12:36:06 -0800 Subject: [PATCH 01/59] script: max length. chain: deadbeef fix. --- lib/bcoin/chain.js | 2 +- lib/bcoin/script.js | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index cce5b0f9..8c7d9abc 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -447,7 +447,7 @@ Chain.prototype.fromJSON = function fromJSON(json) { if (this.index.bloom) this.index.bloom.reset(); else - this.index.bloom = new bcoin.bloom(28 * 1024 * 1024, 16, 0xdeadbee0); + this.index.bloom = new bcoin.bloom(28 * 1024 * 1024, 16, 0xdeadbeef); if (this.index.hashes.length === 0) this.add(new bcoin.block(constants.genesis, 'block')); diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index dd5225dc..1578196f 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -110,6 +110,10 @@ script.subscript = function subscript(s) { }; script.execute = function execute(s, stack, tx) { + if (s.length > 10000) { + return false; + } + for (var pc = 0; pc < s.length; pc++) { var o = s[pc]; if (Array.isArray(o)) { From feda74523f5c081c68f70eeeeffb36981ebe4898 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 9 Jun 2014 16:23:49 -0500 Subject: [PATCH 02/59] peer/pool: add origin satoshi protocol. --- lib/bcoin/chain.js | 51 ++++++++++++++++++++++ lib/bcoin/peer.js | 74 +++++++++++++++++++++++++------- lib/bcoin/pool.js | 104 +++++++++++++++++++++++++++++++++++++++------ 3 files changed, 201 insertions(+), 28 deletions(-) diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index 8c7d9abc..26cd3776 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -37,6 +37,18 @@ function Chain(options) { }; this.request = new utils.RequestCache(); + // Start from the genesis block + // if we're using the original protocol. + if (!this.options.relay) { + preload = { + v: preload.v, + type: preload.type, + hashes: preload.hashes.slice(0, 1), + ts: preload.ts.slice(0, 1), + heights: preload.heights.slice(0, 1) + }; + } + this.fromJSON(preload); // Last TS after preload, needed for fill percent @@ -198,6 +210,31 @@ Chain.prototype.add = function add(block) { return; } + if (!this.options.relay) { + var hash = block.hash('hex'); + var prev = block.prevBlock; + var prevProbe = this._probeIndex(prev, block.ts); + if (prevProbe) { + this._addIndex(hash, block.ts, prevProbe.height + 1); + if (this.orphan.map[hash]) { + delete this.orphan.map[hash]; + this.orphan.count--; + } + this.block.list.push(block); + this._bloomBlock(block); + this.request.fullfill(hash, block); + this._compress(); + return true; + } else { + if (!this.orphan.map[prev]) { + this.orphan.map[prev] = block; + this.orphan.count++; + } + this._compress(); + return false; + } + } + var res = false; var initial = block; do { @@ -388,6 +425,20 @@ Chain.prototype.getLast = function getLast(cb) { return cb(this.index.hashes[this.index.hashes.length - 1]); }; +Chain.prototype.getStartHeight = function getLast(cb) { + if (this.options.relay) { + if (this.options.startHeight != null) { + return this.options.startHeight; + } + return 0; + } + for (var i = 0; i < this.index.heights.length; i++) { + if (this.index.heights[i + 1] !== this.index.heights[i] + 1) { + return this.index.heights[i]; + } + } +}; + Chain.prototype.toJSON = function toJSON() { var keep = 1000; diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index b5b80f5b..1af4efeb 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -89,6 +89,14 @@ Peer.prototype._init = function init() { self._error(err); }); + if (!this.pool.options.relay) { + this.once('version', function() { + var ip = self.socket && self.socket.remoteAddress || '0.0.0.0'; + self.pool.emit('debug', 'version (%s): loading locator hashes for getblocks', ip); + self.loadBlocks(self.pool.locatorHashes(), 0); + }); + } + this._ping.timer = setInterval(function() { self._write(self.framer.ping([ 0xde, 0xad, 0xbe, 0xef, @@ -98,9 +106,7 @@ Peer.prototype._init = function init() { // Send hello this._write(this.framer.version({ - height: this.options.startHeight != null - ? this.options.startHeight - : 0, + height: this.pool.chain.getStartHeight(), relay: this.options.relay })); @@ -167,18 +173,8 @@ Peer.prototype.broadcast = function broadcast(items) { }; Peer.prototype.updateWatch = function updateWatch() { - if (!this.pool.options.relay) { - if (this.ack) { - var self = this; - if (this.pool.block.lastHash) - this.loadBlocks([ self.pool.block.lastHash ], 0); - else - this.pool.chain.getLast(function(hash) { - self.loadBlocks([ hash ], 0); - }); - } + if (!this.pool.options.relay) return; - } if (this.ack) this._write(this.framer.filterLoad(this.bloom, 'none')); @@ -438,11 +434,57 @@ Peer.prototype._handleInv = function handleInv(items) { this.emit('blocks', blocks); if (!this.pool.options.relay) { - if (txs.length) + var self = this; + + if (txs.length) { this.emit('txs', txs.map(function(tx) { return tx.hash; })); - this.getData(items); + // this.getData(txs); + } + + var hashes = blocks.map(utils.toHex); + + var orphans = Object.keys(this.chain.orphan.map).reduce(function(out, prev) { + var orphan = self.chain.orphan.map[prev]; + out[orphan.hash('hex')] = true; + return out; + }, {}); + + hashes = hashes.filter(function(hash) { + return !orphans[hash]; + }); + + // hashes = hashes.filter(function(hash) { + // return !self.chain.index.bloom.test(hash, 'hex') + // && !self.chain.block.bloom.test(hash, 'hex'); + // }); + + if (blocks.length === 1) { + var ip = this.socket && this.socket.remoteAddress || '0.0.0.0'; + this.pool.emit('debug', 'inv (%s): %s', ip, utils.revHex(utils.toHex(blocks[0]))); + } + + if (!hashes.length) { + var ip = this.socket && this.socket.remoteAddress || '0.0.0.0'; + //if (blocks.length === 1 && this._latestBlock) { + if (blocks.length === 1) { + // Already have the latest orphan, another getblocks: + this.pool.emit('debug', 'inv (%s): loading locator hashes for getblocks', ip); + this.loadBlocks(this.pool.locatorHashes(), 0); + } else { + this.pool.emit('debug', 'inv (%s): no hashes to getdata', ip); + } + return; + } + + this.getData(hashes.map(function(hash) { + return { + type: 'block', + hash: hash + }; + })); + return; } diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index 52525c78..c6f8422a 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -40,7 +40,9 @@ function Pool(options) { storage: this.storage, // Since regular blocks contain transactions and full merkle // trees, it's risky to cache 2000 blocks. Let's do 100. - cacheLimit: !this.options.relay ? 100 : null + cacheLimit: !this.options.relay ? 100 : null, + relay: this.options.relay, + startHeight: this.options.startHeight }); this.watchMap = {}; this.bloom = new bcoin.bloom(8 * 1024, @@ -98,10 +100,41 @@ Pool.prototype._init = function _init() { var self = this; this.chain.on('missing', function(hash, preload, parent) { + if (!self.options.relay) return; self._request('block', hash, { force: true }); self._scheduleRequests(); self._loadRange(preload); }); + + if (!this.options.relay) { + // Non-standard stall recovery, + // this shouldn't be necessary. + this._lastLocator = null; + setInterval(function() { + if (!self._lastLocator) return; + if (Date.now() - 60 * 1000 > self._lastLocator) { + self.emit('debug', 'invoking stall recovery!'); + [].concat( + self.peers.pending, + self.peers.block, + self.peers.load + ).reduce(function(out, peer) { + if (peer && !~out.indexOf(peer)) { + out.push(peer); + } + return out; + }, []).forEach(function(peer) { + var ip = peer.socket && peer.socket.remoteAddress || '0.0.0.0'; + self.emit('debug', 'invoking stall recovery on peer (%s)!', ip); + peer.loadBlocks(self.locatorHashes(), 0); + }); + } else { + self.emit('debug', 'stall recovery diff: %d/%d', + self._lastLocator / 1000 | 0, + Date.now() / 1000 | 0); + } + }, 10 * 1000); + } }; Pool.prototype._addLoader = function _addLoader() { @@ -157,6 +190,8 @@ Pool.prototype._addLoader = function _addLoader() { // Split blocks and request them using multiple peers peer.on('blocks', function(hashes) { + if (!self.options.relay) return; + if (hashes.length === 0) { // Reset global load self.block.lastHash = null; @@ -198,6 +233,8 @@ Pool.prototype.isFull = function isFull() { }; Pool.prototype._loadRange = function _loadRange(hashes, force) { + if (!this.options.relay) return; + if (!hashes) return; @@ -220,6 +257,8 @@ Pool.prototype._loadRange = function _loadRange(hashes, force) { }; Pool.prototype._load = function _load() { + if (!this.options.relay) return; + if (this.request.queue.length >= this.load.hwm) { this.load.hiReached = true; return false; @@ -295,19 +334,34 @@ Pool.prototype._addPeer = function _addPeer(backoff) { self._scheduleRequests(); }); - peer.on('merkleblock', function(block) { - // Reset backoff, peer seems to be responsive - backoff = 0; + if (this.options.relay) { + peer.on('merkleblock', function(block) { + // Reset backoff, peer seems to be responsive + backoff = 0; - self._response(block); - self.chain.add(block); - self.emit('chain-progress', self.chain.fillPercent(), peer); - self.emit('block', block, peer); - }); - - if (!this.options.relay) { + self._response(block); + self.chain.add(block); + self.emit('chain-progress', self.chain.fillPercent(), peer); + self.emit('block', block, peer); + }); + } else { peer.on('block', function(block) { - peer.emit('merkleblock', block); + var index = self.chain.index; + backoff = 0; + self._response(block); + var ret = self.chain.add(block); + self.emit('chain-progress', self.chain.fillPercent(), peer); + self.emit('block', block, peer); + if (!ret) { + var ip = peer.socket && peer.socket.remoteAddress || '0.0.0.0'; + self.emit('debug', 'block (%s): %s', ip, utils.revHex(block.hash('hex'))); + self.emit('debug', 'block (%s): loading locator hashes for getblocks', ip); + peer._latestBlock = { hash: block.hash('hex'), ts: block.ts }; + if (!self._latestBlock || peer._latestBlock.ts > self._latestBlock.ts) { + self._latestBlock = peer._latestBlock; + } + peer.loadBlocks(self.locatorHashes(), 0); + } }); } @@ -350,6 +404,32 @@ Pool.prototype._addPeer = function _addPeer(backoff) { }); }; +Pool.prototype.locatorHashes = function() { + var self = this; + var hashes = this.chain.index.hashes; + var indicies = []; + var top = hashes.length - 1; + var step = 1, start = 0; + + for (var i = top; i > 0; i -= step, ++start) { + if (start >= 10) step *= 2; + indicies.push(hashes[i]); + } + + indicies.push(hashes[0]); + + indicies = indicies.reduce(function(out, hash) { + if (!~out.indexOf(hash)) { + out.push(hash); + } + return out; + }, []); + + this._lastLocator = Date.now(); + + return indicies; +}; + Pool.prototype._removePeer = function _removePeer(peer) { var i = this.peers.pending.indexOf(peer); if (i !== -1) From fcc9d661c1e3b823d579ad499cab2579ae0733a9 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 30 Nov 2015 18:15:57 -0800 Subject: [PATCH 03/59] satoshi: fix blockchain download. --- lib/bcoin/chain.js | 31 ++----------- lib/bcoin/peer.js | 60 +++++++++---------------- lib/bcoin/pool.js | 85 +++++++++++++----------------------- lib/bcoin/protocol/framer.js | 10 ++++- lib/bcoin/protocol/parser.js | 41 +++++++++++++++-- 5 files changed, 100 insertions(+), 127 deletions(-) diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index 26cd3776..37cb4df3 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -38,8 +38,8 @@ function Chain(options) { this.request = new utils.RequestCache(); // Start from the genesis block - // if we're using the original protocol. - if (!this.options.relay) { + // if we're a full node. + if (this.options.fullNode) { preload = { v: preload.v, type: preload.type, @@ -210,31 +210,6 @@ Chain.prototype.add = function add(block) { return; } - if (!this.options.relay) { - var hash = block.hash('hex'); - var prev = block.prevBlock; - var prevProbe = this._probeIndex(prev, block.ts); - if (prevProbe) { - this._addIndex(hash, block.ts, prevProbe.height + 1); - if (this.orphan.map[hash]) { - delete this.orphan.map[hash]; - this.orphan.count--; - } - this.block.list.push(block); - this._bloomBlock(block); - this.request.fullfill(hash, block); - this._compress(); - return true; - } else { - if (!this.orphan.map[prev]) { - this.orphan.map[prev] = block; - this.orphan.count++; - } - this._compress(); - return false; - } - } - var res = false; var initial = block; do { @@ -426,7 +401,7 @@ Chain.prototype.getLast = function getLast(cb) { }; Chain.prototype.getStartHeight = function getLast(cb) { - if (this.options.relay) { + if (!this.options.fullNode) { if (this.options.startHeight != null) { return this.options.startHeight; } diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index 1af4efeb..66d8fa76 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -89,7 +89,7 @@ Peer.prototype._init = function init() { self._error(err); }); - if (!this.pool.options.relay) { + if (this.pool.options.fullNode) { this.once('version', function() { var ip = self.socket && self.socket.remoteAddress || '0.0.0.0'; self.pool.emit('debug', 'version (%s): loading locator hashes for getblocks', ip); @@ -173,7 +173,7 @@ Peer.prototype.broadcast = function broadcast(items) { }; Peer.prototype.updateWatch = function updateWatch() { - if (!this.pool.options.relay) + if (this.pool.options.fullNode) return; if (this.ack) @@ -299,6 +299,12 @@ Peer.prototype._onPacket = function onPacket(packet) { else if (cmd === 'getaddr') return this._handleGetAddr(); + if (cmd === 'headers') { + payload = bcoin.block(payload, 'block'); + this.emit(cmd, payload); + return; + } + if (cmd === 'merkleblock' || cmd === 'block') { payload = bcoin.block(payload, cmd); this.lastBlock = payload; @@ -433,58 +439,34 @@ Peer.prototype._handleInv = function handleInv(items) { }); this.emit('blocks', blocks); - if (!this.pool.options.relay) { + if (this.pool.options.fullNode) { var self = this; if (txs.length) { this.emit('txs', txs.map(function(tx) { return tx.hash; })); - // this.getData(txs); + this.getData(txs); } - var hashes = blocks.map(utils.toHex); - var orphans = Object.keys(this.chain.orphan.map).reduce(function(out, prev) { var orphan = self.chain.orphan.map[prev]; - out[orphan.hash('hex')] = true; + out[orphan.hash('hex')] = orphan; return out; }, {}); - hashes = hashes.filter(function(hash) { - return !orphans[hash]; - }); - - // hashes = hashes.filter(function(hash) { - // return !self.chain.index.bloom.test(hash, 'hex') - // && !self.chain.block.bloom.test(hash, 'hex'); - // }); - - if (blocks.length === 1) { - var ip = this.socket && this.socket.remoteAddress || '0.0.0.0'; - this.pool.emit('debug', 'inv (%s): %s', ip, utils.revHex(utils.toHex(blocks[0]))); - } - - if (!hashes.length) { - var ip = this.socket && this.socket.remoteAddress || '0.0.0.0'; - //if (blocks.length === 1 && this._latestBlock) { - if (blocks.length === 1) { - // Already have the latest orphan, another getblocks: - this.pool.emit('debug', 'inv (%s): loading locator hashes for getblocks', ip); - this.loadBlocks(this.pool.locatorHashes(), 0); - } else { - this.pool.emit('debug', 'inv (%s): no hashes to getdata', ip); + for (var i = 0; i < blocks.length; i++) { + var hash = utils.toHex(blocks[i]); + if (orphans[hash]) { + this.loadBlocks(this.pool.locatorHashes(), orphans[hash].prevBlock); + continue; + } + if (!this.chain.index.hashes[hash]) { + this.getData([{ type: 'block', hash: hash }]); + } else if (i === blocks.length - 1) { + this.getData([{ type: 'block', hash: hash }]); } - return; } - - this.getData(hashes.map(function(hash) { - return { - type: 'block', - hash: hash - }; - })); - return; } diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index c6f8422a..fc34bd2f 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -13,7 +13,10 @@ function Pool(options) { EventEmitter.call(this); this.options = options || {}; - this.options.relay = this.options.relay !== false; + this.options.fullNode = !!this.options.fullNode; + this.options.relay == null + ? (this.options.fullNode ? false : true) + : this.options.relay; this.storage = this.options.storage; this.destroyed = false; this.size = options.size || 32; @@ -40,8 +43,8 @@ function Pool(options) { storage: this.storage, // Since regular blocks contain transactions and full merkle // trees, it's risky to cache 2000 blocks. Let's do 100. - cacheLimit: !this.options.relay ? 100 : null, - relay: this.options.relay, + cacheLimit: this.options.fullNode ? 100 : null, + fullNode: this.options.fullNode, startHeight: this.options.startHeight }); this.watchMap = {}; @@ -100,41 +103,11 @@ Pool.prototype._init = function _init() { var self = this; this.chain.on('missing', function(hash, preload, parent) { - if (!self.options.relay) return; + if (self.options.fullNode) return; self._request('block', hash, { force: true }); self._scheduleRequests(); self._loadRange(preload); }); - - if (!this.options.relay) { - // Non-standard stall recovery, - // this shouldn't be necessary. - this._lastLocator = null; - setInterval(function() { - if (!self._lastLocator) return; - if (Date.now() - 60 * 1000 > self._lastLocator) { - self.emit('debug', 'invoking stall recovery!'); - [].concat( - self.peers.pending, - self.peers.block, - self.peers.load - ).reduce(function(out, peer) { - if (peer && !~out.indexOf(peer)) { - out.push(peer); - } - return out; - }, []).forEach(function(peer) { - var ip = peer.socket && peer.socket.remoteAddress || '0.0.0.0'; - self.emit('debug', 'invoking stall recovery on peer (%s)!', ip); - peer.loadBlocks(self.locatorHashes(), 0); - }); - } else { - self.emit('debug', 'stall recovery diff: %d/%d', - self._lastLocator / 1000 | 0, - Date.now() / 1000 | 0); - } - }, 10 * 1000); - } }; Pool.prototype._addLoader = function _addLoader() { @@ -190,7 +163,7 @@ Pool.prototype._addLoader = function _addLoader() { // Split blocks and request them using multiple peers peer.on('blocks', function(hashes) { - if (!self.options.relay) return; + if (self.options.fullNode) return; if (hashes.length === 0) { // Reset global load @@ -233,7 +206,7 @@ Pool.prototype.isFull = function isFull() { }; Pool.prototype._loadRange = function _loadRange(hashes, force) { - if (!this.options.relay) return; + if (this.options.fullNode) return; if (!hashes) return; @@ -257,7 +230,7 @@ Pool.prototype._loadRange = function _loadRange(hashes, force) { }; Pool.prototype._load = function _load() { - if (!this.options.relay) return; + if (this.options.fullNode) return; if (this.request.queue.length >= this.load.hwm) { this.load.hiReached = true; @@ -334,7 +307,7 @@ Pool.prototype._addPeer = function _addPeer(backoff) { self._scheduleRequests(); }); - if (this.options.relay) { + if (!this.options.fullNode) { peer.on('merkleblock', function(block) { // Reset backoff, peer seems to be responsive backoff = 0; @@ -346,22 +319,22 @@ Pool.prototype._addPeer = function _addPeer(backoff) { }); } else { peer.on('block', function(block) { - var index = self.chain.index; backoff = 0; + + // Ignore if we already have. + if (self.chain.index.hashes[block.hash('hex')] + || self.chain.orphan.map[block.prevBlock]) + return; + + // Store as orphan if prevBlock not in main chain + if (self.chain.index.hashes[block.prevBlock]) + peer.loadBlocks(self.locatorHashes(), block.prevBlock); + + // Otherwise, accept block self._response(block); - var ret = self.chain.add(block); + self.chain.add(block); self.emit('chain-progress', self.chain.fillPercent(), peer); self.emit('block', block, peer); - if (!ret) { - var ip = peer.socket && peer.socket.remoteAddress || '0.0.0.0'; - self.emit('debug', 'block (%s): %s', ip, utils.revHex(block.hash('hex'))); - self.emit('debug', 'block (%s): loading locator hashes for getblocks', ip); - peer._latestBlock = { hash: block.hash('hex'), ts: block.ts }; - if (!self._latestBlock || peer._latestBlock.ts > self._latestBlock.ts) { - self._latestBlock = peer._latestBlock; - } - peer.loadBlocks(self.locatorHashes(), 0); - } }); } @@ -409,13 +382,17 @@ Pool.prototype.locatorHashes = function() { var hashes = this.chain.index.hashes; var indicies = []; var top = hashes.length - 1; - var step = 1, start = 0; + var step = 1; - for (var i = top; i > 0; i -= step, ++start) { - if (start >= 10) step *= 2; + for (var j = 0, i = top - 1; j < 10 && i > 0; j++, i--) { indicies.push(hashes[i]); } + for (; i > 0; i -= step) { + indicies.push(hashes[i]); + step *= 2; + } + indicies.push(hashes[0]); indicies = indicies.reduce(function(out, hash) { @@ -425,8 +402,6 @@ Pool.prototype.locatorHashes = function() { return out; }, []); - this._lastLocator = Date.now(); - return indicies; }; diff --git a/lib/bcoin/protocol/framer.js b/lib/bcoin/protocol/framer.js index 64e5b9da..ce6ea7c0 100644 --- a/lib/bcoin/protocol/framer.js +++ b/lib/bcoin/protocol/framer.js @@ -201,7 +201,15 @@ Framer.prototype.filterClear = function filterClear() { return this.packet('filterclear', []); }; +Framer.prototype.getHeaders = function getBlocks(hashes, stop) { + return this._getBlocks('getheaders', hashes, stop); +}; + Framer.prototype.getBlocks = function getBlocks(hashes, stop) { + return this._getBlocks('getblocks', hashes, stop); +}; + +Framer.prototype._getBlocks = function _getBlocks(cmd, hashes, stop) { var p = []; writeU32(p, constants.version, 0); var off = 4 + varint(p, hashes.length, 4); @@ -227,7 +235,7 @@ Framer.prototype.getBlocks = function getBlocks(hashes, stop) { p[off + len] = 0; assert.equal(off + len, p.length); - return this.packet('getblocks', p); + return this.packet(cmd, p); }; Framer.tx = function tx(tx) { diff --git a/lib/bcoin/protocol/parser.js b/lib/bcoin/protocol/parser.js index 7fc165ba..3a2b1da0 100644 --- a/lib/bcoin/protocol/parser.js +++ b/lib/bcoin/protocol/parser.js @@ -97,6 +97,8 @@ Parser.prototype.parsePayload = function parsePayload(cmd, p) { return this.parseInvList(p); else if (cmd === 'merkleblock') return this.parseMerkleBlock(p); + else if (cmd === 'headers') + return this.parseHeaders(p); else if (cmd === 'block') return this.parseBlock(p); else if (cmd === 'tx') @@ -135,10 +137,6 @@ Parser.prototype.parseVersion = function parseVersion(p) { // Relay var relay = p.length > off ? p[off] === 1 : true; - // NOTE: Could just do this to make relay - // `false` when it's not included: - // var relay = p[off] === 1; - return { v: v, services: services, @@ -226,6 +224,41 @@ Parser.prototype.parseMerkleBlock = function parseMerkleBlock(p) { }; }; +Parser.prototype.parseHeaders = function parseHeaders(p) { + if (p.length < 81) + return this._error('Invalid headers size'); + + var result = readIntv(p, 0); + var off = result.off; + var count = result.r; + + var headers = []; + + if (p.length >= off + 81) { + for (var i = 0; i < count; i++) { + var header = {}; + header.version = readU32(p, off); + off += 4; + header.prevBlock = p.slice(off, off + 32); + off += 32; + header.merkleRoot = p.slice(off, off + 32); + off += 32; + header.ts = readU32(p, off); + off += 4; + header.bits = readU32(p, off); + off += 4; + header.nonce = readU32(p, off); + off += 4; + var r = readIntv(p, off); + header.totalTX = r.r; + off += r.off; + headers.push(header); + } + } + + return headers; +}; + Parser.prototype.parseBlock = function parseBlock(p) { if (p.length < 81) return this._error('Invalid block size'); From 3bfd10d0d03586adb5d4d9b65e9f156d567b67d7 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 1 Dec 2015 22:59:26 -0800 Subject: [PATCH 04/59] work --- lib/bcoin/chain.js | 57 ++++++++++++++++++++- lib/bcoin/peer.js | 86 +++++++++++++++++++++++++++----- lib/bcoin/pool.js | 96 +++++++++++++++++++++++++++--------- lib/bcoin/protocol/parser.js | 4 +- lib/bcoin/tx.js | 4 ++ lib/bcoin/utils.js | 4 ++ lib/bcoin/wallet.js | 55 +++++++++++++++++++-- 7 files changed, 264 insertions(+), 42 deletions(-) diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index 37cb4df3..130b234e 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -26,6 +26,7 @@ function Chain(options) { }; this.orphan = { map: {}, + bmap: {}, count: 0 }; this.index = { @@ -65,25 +66,35 @@ function compareTs(a, b) { } Chain.prototype._init = function _init() { + var self = this; + if (!this.storage) return; + utils.nextTick(function() { + self.emit('debug', 'Chain is loading.'); + }); + this.loading = true; - var self = this; + var s = this.storage.createReadStream({ start: this.prefix, end: this.prefix + 'z' }); + s.on('data', function(data) { var hash = data.key.slice(self.prefix.length); self._addIndex(hash, data.value.ts, data.value.height); }); + s.on('error', function(err) { self.emit('error', err); }); + s.on('end', function() { self.loading = false; self.emit('load'); + self.emit('debug', 'Chain successfully loaded.'); }); }; @@ -234,6 +245,7 @@ Chain.prototype.add = function add(block) { if (!this._probeIndex(hash, block.ts) && !prevProbe) { this.orphan.count++; this.orphan.map[prev] = block; + this.orphan.bmap[block.hash('hex')] = block; var range = this._getRange(hash, block.ts, true); var hashes = this.index.hashes.slice(range.start, range.end + 1); @@ -259,6 +271,7 @@ Chain.prototype.add = function add(block) { // We have orphan child for this block - add it to chain block = this.orphan.map[hash]; delete this.orphan.map[hash]; + delete this.orphan.bmap[block.hash('hex')]; this.orphan.count--; } while (true); @@ -414,6 +427,48 @@ Chain.prototype.getStartHeight = function getLast(cb) { } }; +Chain.prototype.getOrphanRoot = function getOrphanRoot(hash) { + var self = this; + + if (typeof hash !== 'string') { + hash = hash.hash('hex'); + } + + /* + var orphans = Object.keys(this.orphan.map).reduce(function(out, prev) { + var orphan = self.orphan.map[prev]; + out[orphan.hash('hex')] = orphan; + return out; + }, {}); + */ + var orphans = this.orphan.bmap; + + /* + var orphanRoot = hash; + var last = orphanRoot; + while (orphans[last]) { + orphanRoot = last; + last = orphans[last].prevBlock; + } + */ + + // accurate: + var orphanRoot = hash; + while (orphans[orphanRoot.prevBlock]) { + orphanRoot = orphans[orphanRoot.prevBlock]; + } + + /* + if hash stop gets last desired block, it should be: + var orphanRoot = hash; + while (orphans[orphanRoot]) { + orphanRoot = orphans[orphanRoot].prevBlock; + } + */ + + return orphanRoot; +}; + Chain.prototype.toJSON = function toJSON() { var keep = 1000; diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index 66d8fa76..e2e7fb9b 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -93,7 +93,10 @@ Peer.prototype._init = function init() { this.once('version', function() { var ip = self.socket && self.socket.remoteAddress || '0.0.0.0'; self.pool.emit('debug', 'version (%s): loading locator hashes for getblocks', ip); - self.loadBlocks(self.pool.locatorHashes(), 0); + self.chain.on('load', function() { + // self.loadBlocks(self.pool.locatorHashes(), 0); + self.loadHeaders(self.pool.locatorHashes(), 0); + }); }); } @@ -288,6 +291,8 @@ Peer.prototype._onPacket = function onPacket(packet) { return this._handleVersion(payload); else if (cmd === 'inv') return this._handleInv(payload); + else if (cmd === 'headers') + return this._handleHeaders(payload); else if (cmd === 'getdata') return this._handleGetData(payload); else if (cmd === 'addr') @@ -299,12 +304,6 @@ Peer.prototype._onPacket = function onPacket(packet) { else if (cmd === 'getaddr') return this._handleGetAddr(); - if (cmd === 'headers') { - payload = bcoin.block(payload, 'block'); - this.emit(cmd, payload); - return; - } - if (cmd === 'merkleblock' || cmd === 'block') { payload = bcoin.block(payload, cmd); this.lastBlock = payload; @@ -439,6 +438,7 @@ Peer.prototype._handleInv = function handleInv(items) { }); this.emit('blocks', blocks); + if (0) if (this.pool.options.fullNode) { var self = this; @@ -449,24 +449,47 @@ Peer.prototype._handleInv = function handleInv(items) { this.getData(txs); } - var orphans = Object.keys(this.chain.orphan.map).reduce(function(out, prev) { + if (blocks.length === 1) { + this._rChainHead = utils.toHex(blocks[0]); + } + + var orph = blocks.filter(function(block) { + return self._requestingOrphan === utils.toHex(block); + }); + + if (orph.length) { + utils.debug('FOUND MISSING BLOCK'); + utils.debug('FOUND MISSING BLOCK'); + utils.debug('FOUND MISSING BLOCK'); + } + + /* + var orphans = Object.keys(self.chain.orphan.map).reduce(function(out, prev) { var orphan = self.chain.orphan.map[prev]; out[orphan.hash('hex')] = orphan; return out; }, {}); + */ + + var orphans = self.chain.orphan.bmap; for (var i = 0; i < blocks.length; i++) { var hash = utils.toHex(blocks[i]); if (orphans[hash]) { - this.loadBlocks(this.pool.locatorHashes(), orphans[hash].prevBlock); + this.loadBlocks(this.pool.locatorHashes(), this.chain.getOrphanRoot(hash)); continue; } - if (!this.chain.index.hashes[hash]) { - this.getData([{ type: 'block', hash: hash }]); - } else if (i === blocks.length - 1) { + if (!this.chain.index.bloom.test(hash, 'hex')) { this.getData([{ type: 'block', hash: hash }]); } } + +/* + this.getData(blocks.map(function(block) { + return { type: 'block', hash: utils.toHex(block) }; + })); +*/ + return; } @@ -479,6 +502,45 @@ Peer.prototype._handleInv = function handleInv(items) { this.getData(txs); }; +Peer.prototype._handleHeaders = function handleHeaders(headers) { + var self = this; + headers = headers.map(function(header) { + header.prevBlock = utils.toHex(header.prevBlock); + header.merkleRoot = utils.toHex(header.merkleRoot); + var abbr = bcoin.block.prototype.abbr.call(header); + header._hash = utils.toHex(utils.dsha256(abbr)); + return header; + }); + + if (this.pool.options.fullNode) { + var self = this; + + if (headers.length === 1) { + this._rChainHead = headers[0]._hash; + } + + var orphans = self.chain.orphan.bmap; + + for (var i = 0; i < headers.length; i++) { + var hash = headers[i]._hash; + if (orphans[hash]) { + this.loadHeaders(this.pool.locatorHashes(), this.chain.getOrphanRoot(hash)); + continue; + } + if (!this.chain.index.bloom.test(hash, 'hex')) { + this.getData([{ type: 'block', hash: hash }]); + } + } + + return; + } +}; + + +Peer.prototype.loadHeaders = function loadBlocks(hashes, stop) { + this._write(this.framer.getHeaders(hashes, stop)); +}; + Peer.prototype.loadBlocks = function loadBlocks(hashes, stop) { this._write(this.framer.getBlocks(hashes, stop)); }; diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index fc34bd2f..d09d85bd 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -96,18 +96,24 @@ inherits(Pool, EventEmitter); module.exports = Pool; Pool.prototype._init = function _init() { + var self = this; + this._addLoader(); for (var i = 0; i < this.size; i++) this._addPeer(0); this._load(); - var self = this; this.chain.on('missing', function(hash, preload, parent) { if (self.options.fullNode) return; self._request('block', hash, { force: true }); self._scheduleRequests(); self._loadRange(preload); }); + + this.chain.on('debug', function() { + var args = Array.prototype.slice.call(arguments); + self.emit.apply(self, ['debug'].concat(args)); + }); }; Pool.prototype._addLoader = function _addLoader() { @@ -148,6 +154,8 @@ Pool.prototype._addLoader = function _addLoader() { clearTimeout(timer); }); + if (this.options.fullNode) return; + function destroy() { // Chain is full and up-to-date if (self.chain.isFull()) { @@ -163,8 +171,6 @@ Pool.prototype._addLoader = function _addLoader() { // Split blocks and request them using multiple peers peer.on('blocks', function(hashes) { - if (self.options.fullNode) return; - if (hashes.length === 0) { // Reset global load self.block.lastHash = null; @@ -321,18 +327,26 @@ Pool.prototype._addPeer = function _addPeer(backoff) { peer.on('block', function(block) { backoff = 0; - // Ignore if we already have. - if (self.chain.index.hashes[block.hash('hex')] - || self.chain.orphan.map[block.prevBlock]) - return; - - // Store as orphan if prevBlock not in main chain - if (self.chain.index.hashes[block.prevBlock]) - peer.loadBlocks(self.locatorHashes(), block.prevBlock); + var hash = block.hash('hex'); + if (hash === peer._requestingOrphan) { + delete peer._requestingOrphan; + } + var height = self.chain.index.hashes.length; // Otherwise, accept block self._response(block); self.chain.add(block); + + if (self.chain.orphan.map[block.prevBlock] || !self.chain.index.bloom.test(block.prevBlock)) { + // if (!self.chain.index.bloom.test(block.prevBlock)) { + if (peer._requestingOrphan) return; + var orphanRoot = self.chain.getOrphanRoot(block); + peer._requestingOrphan = orphanRoot; + peer.loadHeaders(self.locatorHashes(), orphanRoot); + } else if (self.chain.index.hashes.length === height) { + return; + } + self.emit('chain-progress', self.chain.fillPercent(), peer); self.emit('block', block, peer); }); @@ -377,31 +391,65 @@ Pool.prototype._addPeer = function _addPeer(backoff) { }); }; -Pool.prototype.locatorHashes = function() { +Pool.prototype.locatorHashes = function(index) { + var chain = this.chain.index.hashes; + var hashes = []; + var top = chain.length - 1; + var step = 1; + var i; + + if (typeof index === 'string') { + for (i = top; i >= 0; i--) { + if (chain[i] === index) { + top = i; + break; + } + } + } else if (typeof index === 'number') { + top = index; + } + + i = top; + for (;;) { + hashes.push(chain[i]); + i = i - step; + if (i <= 0) { + hashes.push(chain[0]); + break; + } + if (hashes.length >= 10) { + step *= 2; + } + } + + return hashes; +}; + +Pool.prototype.locatorHashes = function(index) { var self = this; var hashes = this.chain.index.hashes; var indicies = []; var top = hashes.length - 1; - var step = 1; + var step = 1, start = 0; - for (var j = 0, i = top - 1; j < 10 && i > 0; j++, i--) { - indicies.push(hashes[i]); + if (typeof index === 'string') { + for (i = top; i >= 0; i--) { + if (chain[i] === index) { + top = i; + break; + } + } + } else if (typeof index === 'number') { + top = index; } - for (; i > 0; i -= step) { + for (var i = top; i > 0; i -= step, ++start) { + if (start >= 10) step *= 2; indicies.push(hashes[i]); - step *= 2; } indicies.push(hashes[0]); - indicies = indicies.reduce(function(out, hash) { - if (!~out.indexOf(hash)) { - out.push(hash); - } - return out; - }, []); - return indicies; }; diff --git a/lib/bcoin/protocol/parser.js b/lib/bcoin/protocol/parser.js index 3a2b1da0..39ecf24d 100644 --- a/lib/bcoin/protocol/parser.js +++ b/lib/bcoin/protocol/parser.js @@ -235,7 +235,7 @@ Parser.prototype.parseHeaders = function parseHeaders(p) { var headers = []; if (p.length >= off + 81) { - for (var i = 0; i < count; i++) { + for (var i = 0; i < count && off + 81 < p.length; i++) { var header = {}; header.version = readU32(p, off); off += 4; @@ -251,7 +251,7 @@ Parser.prototype.parseHeaders = function parseHeaders(p) { off += 4; var r = readIntv(p, off); header.totalTX = r.r; - off += r.off; + off = r.off; headers.push(header); } } diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 7b15e799..970784d0 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -300,3 +300,7 @@ TX.fromJSON = function fromJSON(json) { return tx; }; + +TX.prototype.clone = function clone() { + return new TX(new bcoin.protocol.parser().parseTX(this.render())); +}; diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index 44ac8387..748193c1 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -466,3 +466,7 @@ utils.isIP = function(ip) { return 0; }; + +utils.debug = function(msg) { + console.log('\x1b[31m' + msg + '\x1b[m'); +}; diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 877f7a44..65b64919 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -250,6 +250,33 @@ Wallet.prototype.sign = function sign(tx, type, inputs, off) { return inputs.length; }; +Wallet.prototype.signEmpty = function sign(tx, type, inputs, off) { + if (!type) + type = 'all'; + assert.equal(type, 'all'); + + if (!off) + off = 0; + + var pub = this.getPublicKey(); + inputs = inputs || tx.inputs; + + // Add signature script to each input + inputs = inputs.filter(function(input, i) { + // Filter inputs that this wallet own + if (!input.out.tx || !this.ownOutput(input.out.tx)) + return false; + + var signature = [0x30, 0, 0x02, 0, 0, 0x02, 0, 0]; + signature = signature.concat(bcoin.protocol.constants.hashType[type]); + + input.script = [ signature, pub ]; + return true; + }, this); + + return inputs.length; +}; + Wallet.prototype.addTX = function addTX(tx, block) { return this.tx.add(tx); }; @@ -270,9 +297,17 @@ Wallet.prototype.balance = function balance() { return this.tx.balance(); }; -Wallet.prototype.fill = function fill(tx, cb) { +Wallet.prototype.fill = function fill(tx, options, cb) { + if ((cb && typeof cb === 'object') || options == null) { + cb = options; + options = {}; + } cb = utils.asyncify(cb); + if (options._getChange) { + tx = tx.clone(); + } + // NOTE: tx should be prefilled with all outputs var cost = tx.funds('out'); @@ -296,7 +331,7 @@ Wallet.prototype.fill = function fill(tx, cb) { unspent.every(addInput, this); // Add dummy output (for `left`) to calculate maximum TX size - tx.out(this, new bn(0)); + tx.out(options.change || this, new bn(0)); // Change fee value if it is more than 1024 bytes // (10000 satoshi for every 1024 bytes) @@ -323,6 +358,10 @@ Wallet.prototype.fill = function fill(tx, cb) { // How much money is left after sending outputs var left = tx.funds('in').sub(total); + if (options._getChange) { + return left; + } + // Not enough money, transfer everything to owner if (left.cmpn(this.dust) < 0) { // NOTE: that this output is either `postCost` or one of the `dust` values @@ -337,9 +376,19 @@ Wallet.prototype.fill = function fill(tx, cb) { tx.outputs[tx.outputs.length - 1].value = left; // Sign transaction - this.sign(tx); + if (options.sign === false) { + this.signEmpty(tx); + } else { + this.sign(tx); + } cb(null, tx); + + return tx; +}; + +Wallet.prototype.getChange = function fill(tx) { + return this.fill(tx, { _getChange: true }); }; Wallet.prototype.toJSON = function toJSON() { From ea9af49f4bb18b0123124ea11a9fd64242ce6595 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 4 Dec 2015 17:58:21 -0800 Subject: [PATCH 05/59] hd and p2sh --- etc/english.json | 2050 +++++++++++++++++++++++++++++++ lib/bcoin.js | 7 +- lib/bcoin/chain.js | 79 +- lib/bcoin/hd.js | 739 +++++++++++ lib/bcoin/peer.js | 84 +- lib/bcoin/pool.js | 86 +- lib/bcoin/protocol/constants.js | 9 + lib/bcoin/protocol/parser.js | 2 + lib/bcoin/script.js | 1 + lib/bcoin/tx.js | 53 +- lib/bcoin/utils.js | 14 + lib/bcoin/wallet.js | 134 +- 12 files changed, 3085 insertions(+), 173 deletions(-) create mode 100644 etc/english.json create mode 100644 lib/bcoin/hd.js diff --git a/etc/english.json b/etc/english.json new file mode 100644 index 00000000..5bc5c639 --- /dev/null +++ b/etc/english.json @@ -0,0 +1,2050 @@ +[ + "abandon", + "ability", + "able", + "about", + "above", + "absent", + "absorb", + "abstract", + "absurd", + "abuse", + "access", + "accident", + "account", + "accuse", + "achieve", + "acid", + "acoustic", + "acquire", + "across", + "act", + "action", + "actor", + "actress", + "actual", + "adapt", + "add", + "addict", + "address", + "adjust", + "admit", + "adult", + "advance", + "advice", + "aerobic", + "affair", + "afford", + "afraid", + "again", + "age", + "agent", + "agree", + "ahead", + "aim", + "air", + "airport", + "aisle", + "alarm", + "album", + "alcohol", + "alert", + "alien", + "all", + "alley", + "allow", + "almost", + "alone", + "alpha", + "already", + "also", + "alter", + "always", + "amateur", + "amazing", + "among", + "amount", + "amused", + "analyst", + "anchor", + "ancient", + "anger", + "angle", + "angry", + "animal", + "ankle", + "announce", + "annual", + "another", + "answer", + "antenna", + "antique", + "anxiety", + "any", + "apart", + "apology", + "appear", + "apple", + "approve", + "april", + "arch", + "arctic", + "area", + "arena", + "argue", + "arm", + "armed", + "armor", + "army", + "around", + "arrange", + "arrest", + "arrive", + "arrow", + "art", + "artefact", + "artist", + "artwork", + "ask", + "aspect", + "assault", + "asset", + "assist", + "assume", + "asthma", + "athlete", + "atom", + "attack", + "attend", + "attitude", + "attract", + "auction", + "audit", + "august", + "aunt", + "author", + "auto", + "autumn", + "average", + "avocado", + "avoid", + "awake", + "aware", + "away", + "awesome", + "awful", + "awkward", + "axis", + "baby", + "bachelor", + "bacon", + "badge", + "bag", + "balance", + "balcony", + "ball", + "bamboo", + "banana", + "banner", + "bar", + "barely", + "bargain", + "barrel", + "base", + "basic", + "basket", + "battle", + "beach", + "bean", + "beauty", + "because", + "become", + "beef", + "before", + "begin", + "behave", + "behind", + "believe", + "below", + "belt", + "bench", + "benefit", + "best", + "betray", + "better", + "between", + "beyond", + "bicycle", + "bid", + "bike", + "bind", + "biology", + "bird", + "birth", + "bitter", + "black", + "blade", + "blame", + "blanket", + "blast", + "bleak", + "bless", + "blind", + "blood", + "blossom", + "blouse", + "blue", + "blur", + "blush", + "board", + "boat", + "body", + "boil", + "bomb", + "bone", + "bonus", + "book", + "boost", + "border", + "boring", + "borrow", + "boss", + "bottom", + "bounce", + "box", + "boy", + "bracket", + "brain", + "brand", + "brass", + "brave", + "bread", + "breeze", + "brick", + "bridge", + "brief", + "bright", + "bring", + "brisk", + "broccoli", + "broken", + "bronze", + "broom", + "brother", + "brown", + "brush", + "bubble", + "buddy", + "budget", + "buffalo", + "build", + "bulb", + "bulk", + "bullet", + "bundle", + "bunker", + "burden", + "burger", + "burst", + "bus", + "business", + "busy", + "butter", + "buyer", + "buzz", + "cabbage", + "cabin", + "cable", + "cactus", + "cage", + "cake", + "call", + "calm", + "camera", + "camp", + "can", + "canal", + "cancel", + "candy", + "cannon", + "canoe", + "canvas", + "canyon", + "capable", + "capital", + "captain", + "car", + "carbon", + "card", + "cargo", + "carpet", + "carry", + "cart", + "case", + "cash", + "casino", + "castle", + "casual", + "cat", + "catalog", + "catch", + "category", + "cattle", + "caught", + "cause", + "caution", + "cave", + "ceiling", + "celery", + "cement", + "census", + "century", + "cereal", + "certain", + "chair", + "chalk", + "champion", + "change", + "chaos", + "chapter", + "charge", + "chase", + "chat", + "cheap", + "check", + "cheese", + "chef", + "cherry", + "chest", + "chicken", + "chief", + "child", + "chimney", + "choice", + "choose", + "chronic", + "chuckle", + "chunk", + "churn", + "cigar", + "cinnamon", + "circle", + "citizen", + "city", + "civil", + "claim", + "clap", + "clarify", + "claw", + "clay", + "clean", + "clerk", + "clever", + "click", + "client", + "cliff", + "climb", + "clinic", + "clip", + "clock", + "clog", + "close", + "cloth", + "cloud", + "clown", + "club", + "clump", + "cluster", + "clutch", + "coach", + "coast", + "coconut", + "code", + "coffee", + "coil", + "coin", + "collect", + "color", + "column", + "combine", + "come", + "comfort", + "comic", + "common", + "company", + "concert", + "conduct", + "confirm", + "congress", + "connect", + "consider", + "control", + "convince", + "cook", + "cool", + "copper", + "copy", + "coral", + "core", + "corn", + "correct", + "cost", + "cotton", + "couch", + "country", + "couple", + "course", + "cousin", + "cover", + "coyote", + "crack", + "cradle", + "craft", + "cram", + "crane", + "crash", + "crater", + "crawl", + "crazy", + "cream", + "credit", + "creek", + "crew", + "cricket", + "crime", + "crisp", + "critic", + "crop", + "cross", + "crouch", + "crowd", + "crucial", + "cruel", + "cruise", + "crumble", + "crunch", + "crush", + "cry", + "crystal", + "cube", + "culture", + "cup", + "cupboard", + "curious", + "current", + "curtain", + "curve", + "cushion", + "custom", + "cute", + "cycle", + "dad", + "damage", + "damp", + "dance", + "danger", + "daring", + "dash", + "daughter", + "dawn", + "day", + "deal", + "debate", + "debris", + "decade", + "december", + "decide", + "decline", + "decorate", + "decrease", + "deer", + "defense", + "define", + "defy", + "degree", + "delay", + "deliver", + "demand", + "demise", + "denial", + "dentist", + "deny", + "depart", + "depend", + "deposit", + "depth", + "deputy", + "derive", + "describe", + "desert", + "design", + "desk", + "despair", + "destroy", + "detail", + "detect", + "develop", + "device", + "devote", + "diagram", + "dial", + "diamond", + "diary", + "dice", + "diesel", + "diet", + "differ", + "digital", + "dignity", + "dilemma", + "dinner", + "dinosaur", + "direct", + "dirt", + "disagree", + "discover", + "disease", + "dish", + "dismiss", + "disorder", + "display", + "distance", + "divert", + "divide", + "divorce", + "dizzy", + "doctor", + "document", + "dog", + "doll", + "dolphin", + "domain", + "donate", + "donkey", + "donor", + "door", + "dose", + "double", + "dove", + "draft", + "dragon", + "drama", + "drastic", + "draw", + "dream", + "dress", + "drift", + "drill", + "drink", + "drip", + "drive", + "drop", + "drum", + "dry", + "duck", + "dumb", + "dune", + "during", + "dust", + "dutch", + "duty", + "dwarf", + "dynamic", + "eager", + "eagle", + "early", + "earn", + "earth", + "easily", + "east", + "easy", + "echo", + "ecology", + "economy", + "edge", + "edit", + "educate", + "effort", + "egg", + "eight", + "either", + "elbow", + "elder", + "electric", + "elegant", + "element", + "elephant", + "elevator", + "elite", + "else", + "embark", + "embody", + "embrace", + "emerge", + "emotion", + "employ", + "empower", + "empty", + "enable", + "enact", + "end", + "endless", + "endorse", + "enemy", + "energy", + "enforce", + "engage", + "engine", + "enhance", + "enjoy", + "enlist", + "enough", + "enrich", + "enroll", + "ensure", + "enter", + "entire", + "entry", + "envelope", + "episode", + "equal", + "equip", + "era", + "erase", + "erode", + "erosion", + "error", + "erupt", + "escape", + "essay", + "essence", + "estate", + "eternal", + "ethics", + "evidence", + "evil", + "evoke", + "evolve", + "exact", + "example", + "excess", + "exchange", + "excite", + "exclude", + "excuse", + "execute", + "exercise", + "exhaust", + "exhibit", + "exile", + "exist", + "exit", + "exotic", + "expand", + "expect", + "expire", + "explain", + "expose", + "express", + "extend", + "extra", + "eye", + "eyebrow", + "fabric", + "face", + "faculty", + "fade", + "faint", + "faith", + "fall", + "false", + "fame", + "family", + "famous", + "fan", + "fancy", + "fantasy", + "farm", + "fashion", + "fat", + "fatal", + "father", + "fatigue", + "fault", + "favorite", + "feature", + "february", + "federal", + "fee", + "feed", + "feel", + "female", + "fence", + "festival", + "fetch", + "fever", + "few", + "fiber", + "fiction", + "field", + "figure", + "file", + "film", + "filter", + "final", + "find", + "fine", + "finger", + "finish", + "fire", + "firm", + "first", + "fiscal", + "fish", + "fit", + "fitness", + "fix", + "flag", + "flame", + "flash", + "flat", + "flavor", + "flee", + "flight", + "flip", + "float", + "flock", + "floor", + "flower", + "fluid", + "flush", + "fly", + "foam", + "focus", + "fog", + "foil", + "fold", + "follow", + "food", + "foot", + "force", + "forest", + "forget", + "fork", + "fortune", + "forum", + "forward", + "fossil", + "foster", + "found", + "fox", + "fragile", + "frame", + "frequent", + "fresh", + "friend", + "fringe", + "frog", + "front", + "frost", + "frown", + "frozen", + "fruit", + "fuel", + "fun", + "funny", + "furnace", + "fury", + "future", + "gadget", + "gain", + "galaxy", + "gallery", + "game", + "gap", + "garage", + "garbage", + "garden", + "garlic", + "garment", + "gas", + "gasp", + "gate", + "gather", + "gauge", + "gaze", + "general", + "genius", + "genre", + "gentle", + "genuine", + "gesture", + "ghost", + "giant", + "gift", + "giggle", + "ginger", + "giraffe", + "girl", + "give", + "glad", + "glance", + "glare", + "glass", + "glide", + "glimpse", + "globe", + "gloom", + "glory", + "glove", + "glow", + "glue", + "goat", + "goddess", + "gold", + "good", + "goose", + "gorilla", + "gospel", + "gossip", + "govern", + "gown", + "grab", + "grace", + "grain", + "grant", + "grape", + "grass", + "gravity", + "great", + "green", + "grid", + "grief", + "grit", + "grocery", + "group", + "grow", + "grunt", + "guard", + "guess", + "guide", + "guilt", + "guitar", + "gun", + "gym", + "habit", + "hair", + "half", + "hammer", + "hamster", + "hand", + "happy", + "harbor", + "hard", + "harsh", + "harvest", + "hat", + "have", + "hawk", + "hazard", + "head", + "health", + "heart", + "heavy", + "hedgehog", + "height", + "hello", + "helmet", + "help", + "hen", + "hero", + "hidden", + "high", + "hill", + "hint", + "hip", + "hire", + "history", + "hobby", + "hockey", + "hold", + "hole", + "holiday", + "hollow", + "home", + "honey", + "hood", + "hope", + "horn", + "horror", + "horse", + "hospital", + "host", + "hotel", + "hour", + "hover", + "hub", + "huge", + "human", + "humble", + "humor", + "hundred", + "hungry", + "hunt", + "hurdle", + "hurry", + "hurt", + "husband", + "hybrid", + "ice", + "icon", + "idea", + "identify", + "idle", + "ignore", + "ill", + "illegal", + "illness", + "image", + "imitate", + "immense", + "immune", + "impact", + "impose", + "improve", + "impulse", + "inch", + "include", + "income", + "increase", + "index", + "indicate", + "indoor", + "industry", + "infant", + "inflict", + "inform", + "inhale", + "inherit", + "initial", + "inject", + "injury", + "inmate", + "inner", + "innocent", + "input", + "inquiry", + "insane", + "insect", + "inside", + "inspire", + "install", + "intact", + "interest", + "into", + "invest", + "invite", + "involve", + "iron", + "island", + "isolate", + "issue", + "item", + "ivory", + "jacket", + "jaguar", + "jar", + "jazz", + "jealous", + "jeans", + "jelly", + "jewel", + "job", + "join", + "joke", + "journey", + "joy", + "judge", + "juice", + "jump", + "jungle", + "junior", + "junk", + "just", + "kangaroo", + "keen", + "keep", + "ketchup", + "key", + "kick", + "kid", + "kidney", + "kind", + "kingdom", + "kiss", + "kit", + "kitchen", + "kite", + "kitten", + "kiwi", + "knee", + "knife", + "knock", + "know", + "lab", + "label", + "labor", + "ladder", + "lady", + "lake", + "lamp", + "language", + "laptop", + "large", + "later", + "latin", + "laugh", + "laundry", + "lava", + "law", + "lawn", + "lawsuit", + "layer", + "lazy", + "leader", + "leaf", + "learn", + "leave", + "lecture", + "left", + "leg", + "legal", + "legend", + "leisure", + "lemon", + "lend", + "length", + "lens", + "leopard", + "lesson", + "letter", + "level", + "liar", + "liberty", + "library", + "license", + "life", + "lift", + "light", + "like", + "limb", + "limit", + "link", + "lion", + "liquid", + "list", + "little", + "live", + "lizard", + "load", + "loan", + "lobster", + "local", + "lock", + "logic", + "lonely", + "long", + "loop", + "lottery", + "loud", + "lounge", + "love", + "loyal", + "lucky", + "luggage", + "lumber", + "lunar", + "lunch", + "luxury", + "lyrics", + "machine", + "mad", + "magic", + "magnet", + "maid", + "mail", + "main", + "major", + "make", + "mammal", + "man", + "manage", + "mandate", + "mango", + "mansion", + "manual", + "maple", + "marble", + "march", + "margin", + "marine", + "market", + "marriage", + "mask", + "mass", + "master", + "match", + "material", + "math", + "matrix", + "matter", + "maximum", + "maze", + "meadow", + "mean", + "measure", + "meat", + "mechanic", + "medal", + "media", + "melody", + "melt", + "member", + "memory", + "mention", + "menu", + "mercy", + "merge", + "merit", + "merry", + "mesh", + "message", + "metal", + "method", + "middle", + "midnight", + "milk", + "million", + "mimic", + "mind", + "minimum", + "minor", + "minute", + "miracle", + "mirror", + "misery", + "miss", + "mistake", + "mix", + "mixed", + "mixture", + "mobile", + "model", + "modify", + "mom", + "moment", + "monitor", + "monkey", + "monster", + "month", + "moon", + "moral", + "more", + "morning", + "mosquito", + "mother", + "motion", + "motor", + "mountain", + "mouse", + "move", + "movie", + "much", + "muffin", + "mule", + "multiply", + "muscle", + "museum", + "mushroom", + "music", + "must", + "mutual", + "myself", + "mystery", + "myth", + "naive", + "name", + "napkin", + "narrow", + "nasty", + "nation", + "nature", + "near", + "neck", + "need", + "negative", + "neglect", + "neither", + "nephew", + "nerve", + "nest", + "net", + "network", + "neutral", + "never", + "news", + "next", + "nice", + "night", + "noble", + "noise", + "nominee", + "noodle", + "normal", + "north", + "nose", + "notable", + "note", + "nothing", + "notice", + "novel", + "now", + "nuclear", + "number", + "nurse", + "nut", + "oak", + "obey", + "object", + "oblige", + "obscure", + "observe", + "obtain", + "obvious", + "occur", + "ocean", + "october", + "odor", + "off", + "offer", + "office", + "often", + "oil", + "okay", + "old", + "olive", + "olympic", + "omit", + "once", + "one", + "onion", + "online", + "only", + "open", + "opera", + "opinion", + "oppose", + "option", + "orange", + "orbit", + "orchard", + "order", + "ordinary", + "organ", + "orient", + "original", + "orphan", + "ostrich", + "other", + "outdoor", + "outer", + "output", + "outside", + "oval", + "oven", + "over", + "own", + "owner", + "oxygen", + "oyster", + "ozone", + "pact", + "paddle", + "page", + "pair", + "palace", + "palm", + "panda", + "panel", + "panic", + "panther", + "paper", + "parade", + "parent", + "park", + "parrot", + "party", + "pass", + "patch", + "path", + "patient", + "patrol", + "pattern", + "pause", + "pave", + "payment", + "peace", + "peanut", + "pear", + "peasant", + "pelican", + "pen", + "penalty", + "pencil", + "people", + "pepper", + "perfect", + "permit", + "person", + "pet", + "phone", + "photo", + "phrase", + "physical", + "piano", + "picnic", + "picture", + "piece", + "pig", + "pigeon", + "pill", + "pilot", + "pink", + "pioneer", + "pipe", + "pistol", + "pitch", + "pizza", + "place", + "planet", + "plastic", + "plate", + "play", + "please", + "pledge", + "pluck", + "plug", + "plunge", + "poem", + "poet", + "point", + "polar", + "pole", + "police", + "pond", + "pony", + "pool", + "popular", + "portion", + "position", + "possible", + "post", + "potato", + "pottery", + "poverty", + "powder", + "power", + "practice", + "praise", + "predict", + "prefer", + "prepare", + "present", + "pretty", + "prevent", + "price", + "pride", + "primary", + "print", + "priority", + "prison", + "private", + "prize", + "problem", + "process", + "produce", + "profit", + "program", + "project", + "promote", + "proof", + "property", + "prosper", + "protect", + "proud", + "provide", + "public", + "pudding", + "pull", + "pulp", + "pulse", + "pumpkin", + "punch", + "pupil", + "puppy", + "purchase", + "purity", + "purpose", + "purse", + "push", + "put", + "puzzle", + "pyramid", + "quality", + "quantum", + "quarter", + "question", + "quick", + "quit", + "quiz", + "quote", + "rabbit", + "raccoon", + "race", + "rack", + "radar", + "radio", + "rail", + "rain", + "raise", + "rally", + "ramp", + "ranch", + "random", + "range", + "rapid", + "rare", + "rate", + "rather", + "raven", + "raw", + "razor", + "ready", + "real", + "reason", + "rebel", + "rebuild", + "recall", + "receive", + "recipe", + "record", + "recycle", + "reduce", + "reflect", + "reform", + "refuse", + "region", + "regret", + "regular", + "reject", + "relax", + "release", + "relief", + "rely", + "remain", + "remember", + "remind", + "remove", + "render", + "renew", + "rent", + "reopen", + "repair", + "repeat", + "replace", + "report", + "require", + "rescue", + "resemble", + "resist", + "resource", + "response", + "result", + "retire", + "retreat", + "return", + "reunion", + "reveal", + "review", + "reward", + "rhythm", + "rib", + "ribbon", + "rice", + "rich", + "ride", + "ridge", + "rifle", + "right", + "rigid", + "ring", + "riot", + "ripple", + "risk", + "ritual", + "rival", + "river", + "road", + "roast", + "robot", + "robust", + "rocket", + "romance", + "roof", + "rookie", + "room", + "rose", + "rotate", + "rough", + "round", + "route", + "royal", + "rubber", + "rude", + "rug", + "rule", + "run", + "runway", + "rural", + "sad", + "saddle", + "sadness", + "safe", + "sail", + "salad", + "salmon", + "salon", + "salt", + "salute", + "same", + "sample", + "sand", + "satisfy", + "satoshi", + "sauce", + "sausage", + "save", + "say", + "scale", + "scan", + "scare", + "scatter", + "scene", + "scheme", + "school", + "science", + "scissors", + "scorpion", + "scout", + "scrap", + "screen", + "script", + "scrub", + "sea", + "search", + "season", + "seat", + "second", + "secret", + "section", + "security", + "seed", + "seek", + "segment", + "select", + "sell", + "seminar", + "senior", + "sense", + "sentence", + "series", + "service", + "session", + "settle", + "setup", + "seven", + "shadow", + "shaft", + "shallow", + "share", + "shed", + "shell", + "sheriff", + "shield", + "shift", + "shine", + "ship", + "shiver", + "shock", + "shoe", + "shoot", + "shop", + "short", + "shoulder", + "shove", + "shrimp", + "shrug", + "shuffle", + "shy", + "sibling", + "sick", + "side", + "siege", + "sight", + "sign", + "silent", + "silk", + "silly", + "silver", + "similar", + "simple", + "since", + "sing", + "siren", + "sister", + "situate", + "six", + "size", + "skate", + "sketch", + "ski", + "skill", + "skin", + "skirt", + "skull", + "slab", + "slam", + "sleep", + "slender", + "slice", + "slide", + "slight", + "slim", + "slogan", + "slot", + "slow", + "slush", + "small", + "smart", + "smile", + "smoke", + "smooth", + "snack", + "snake", + "snap", + "sniff", + "snow", + "soap", + "soccer", + "social", + "sock", + "soda", + "soft", + "solar", + "soldier", + "solid", + "solution", + "solve", + "someone", + "song", + "soon", + "sorry", + "sort", + "soul", + "sound", + "soup", + "source", + "south", + "space", + "spare", + "spatial", + "spawn", + "speak", + "special", + "speed", + "spell", + "spend", + "sphere", + "spice", + "spider", + "spike", + "spin", + "spirit", + "split", + "spoil", + "sponsor", + "spoon", + "sport", + "spot", + "spray", + "spread", + "spring", + "spy", + "square", + "squeeze", + "squirrel", + "stable", + "stadium", + "staff", + "stage", + "stairs", + "stamp", + "stand", + "start", + "state", + "stay", + "steak", + "steel", + "stem", + "step", + "stereo", + "stick", + "still", + "sting", + "stock", + "stomach", + "stone", + "stool", + "story", + "stove", + "strategy", + "street", + "strike", + "strong", + "struggle", + "student", + "stuff", + "stumble", + "style", + "subject", + "submit", + "subway", + "success", + "such", + "sudden", + "suffer", + "sugar", + "suggest", + "suit", + "summer", + "sun", + "sunny", + "sunset", + "super", + "supply", + "supreme", + "sure", + "surface", + "surge", + "surprise", + "surround", + "survey", + "suspect", + "sustain", + "swallow", + "swamp", + "swap", + "swarm", + "swear", + "sweet", + "swift", + "swim", + "swing", + "switch", + "sword", + "symbol", + "symptom", + "syrup", + "system", + "table", + "tackle", + "tag", + "tail", + "talent", + "talk", + "tank", + "tape", + "target", + "task", + "taste", + "tattoo", + "taxi", + "teach", + "team", + "tell", + "ten", + "tenant", + "tennis", + "tent", + "term", + "test", + "text", + "thank", + "that", + "theme", + "then", + "theory", + "there", + "they", + "thing", + "this", + "thought", + "three", + "thrive", + "throw", + "thumb", + "thunder", + "ticket", + "tide", + "tiger", + "tilt", + "timber", + "time", + "tiny", + "tip", + "tired", + "tissue", + "title", + "toast", + "tobacco", + "today", + "toddler", + "toe", + "together", + "toilet", + "token", + "tomato", + "tomorrow", + "tone", + "tongue", + "tonight", + "tool", + "tooth", + "top", + "topic", + "topple", + "torch", + "tornado", + "tortoise", + "toss", + "total", + "tourist", + "toward", + "tower", + "town", + "toy", + "track", + "trade", + "traffic", + "tragic", + "train", + "transfer", + "trap", + "trash", + "travel", + "tray", + "treat", + "tree", + "trend", + "trial", + "tribe", + "trick", + "trigger", + "trim", + "trip", + "trophy", + "trouble", + "truck", + "true", + "truly", + "trumpet", + "trust", + "truth", + "try", + "tube", + "tuition", + "tumble", + "tuna", + "tunnel", + "turkey", + "turn", + "turtle", + "twelve", + "twenty", + "twice", + "twin", + "twist", + "two", + "type", + "typical", + "ugly", + "umbrella", + "unable", + "unaware", + "uncle", + "uncover", + "under", + "undo", + "unfair", + "unfold", + "unhappy", + "uniform", + "unique", + "unit", + "universe", + "unknown", + "unlock", + "until", + "unusual", + "unveil", + "update", + "upgrade", + "uphold", + "upon", + "upper", + "upset", + "urban", + "urge", + "usage", + "use", + "used", + "useful", + "useless", + "usual", + "utility", + "vacant", + "vacuum", + "vague", + "valid", + "valley", + "valve", + "van", + "vanish", + "vapor", + "various", + "vast", + "vault", + "vehicle", + "velvet", + "vendor", + "venture", + "venue", + "verb", + "verify", + "version", + "very", + "vessel", + "veteran", + "viable", + "vibrant", + "vicious", + "victory", + "video", + "view", + "village", + "vintage", + "violin", + "virtual", + "virus", + "visa", + "visit", + "visual", + "vital", + "vivid", + "vocal", + "voice", + "void", + "volcano", + "volume", + "vote", + "voyage", + "wage", + "wagon", + "wait", + "walk", + "wall", + "walnut", + "want", + "warfare", + "warm", + "warrior", + "wash", + "wasp", + "waste", + "water", + "wave", + "way", + "wealth", + "weapon", + "wear", + "weasel", + "weather", + "web", + "wedding", + "weekend", + "weird", + "welcome", + "west", + "wet", + "whale", + "what", + "wheat", + "wheel", + "when", + "where", + "whip", + "whisper", + "wide", + "width", + "wife", + "wild", + "will", + "win", + "window", + "wine", + "wing", + "wink", + "winner", + "winter", + "wire", + "wisdom", + "wise", + "wish", + "witness", + "wolf", + "woman", + "wonder", + "wood", + "wool", + "word", + "work", + "world", + "worry", + "worth", + "wrap", + "wreck", + "wrestle", + "wrist", + "write", + "wrong", + "yard", + "year", + "yellow", + "you", + "young", + "youth", + "zebra", + "zero", + "zone", + "zoo" +] diff --git a/lib/bcoin.js b/lib/bcoin.js index 6ef8958e..7b64c3c2 100644 --- a/lib/bcoin.js +++ b/lib/bcoin.js @@ -1,7 +1,11 @@ var bcoin = exports; var elliptic = require('elliptic'); -bcoin.ecdsa = elliptic.ecdsa(elliptic.nist.secp256k1); +if (elliptic.ec) { + bcoin.ecdsa = elliptic.ec('secp256k1'); +} else { + bcoin.ecdsa = elliptic.ecdsa(elliptic.nist.secp256k1); +} bcoin.utils = require('./bcoin/utils'); bcoin.bloom = require('./bcoin/bloom'); bcoin.protocol = require('./bcoin/protocol'); @@ -13,3 +17,4 @@ bcoin.chain = require('./bcoin/chain'); bcoin.wallet = require('./bcoin/wallet'); bcoin.peer = require('./bcoin/peer'); bcoin.pool = require('./bcoin/pool'); +bcoin.hd = require('./bcoin/hd'); diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index 130b234e..ad38b748 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -427,6 +427,76 @@ Chain.prototype.getStartHeight = function getLast(cb) { } }; +Chain.prototype.locatorHashes = function(index) { + var chain = this.index.hashes; + var hashes = []; + var top = chain.length - 1; + var step = 1; + var i; + + if (typeof index === 'string') { + for (i = top; i >= 0; i--) { + if (chain[i] === index) { + top = i; + break; + } + } + } else if (typeof index === 'number') { + top = index; + } + + i = top; + for (;;) { + hashes.push(chain[i]); + i = i - step; + if (i <= 0) { + hashes.push(chain[0]); + break; + } + if (hashes.length >= 10) { + step *= 2; + } + } + + return hashes; +}; + +Chain.prototype.locatorHashes = function(index) { + var chain = this.index.hashes; + var hashes = []; + var top = chain.length - 1; + var step = 1, start = 0; + + if (typeof index === 'string') { + for (i = top; i >= 0; i--) { + if (chain[i] === index) { + top = i; + break; + } + } + } else if (typeof index === 'number') { + top = index; + } + + for (var i = top; i > 0; i -= step, ++start) { + if (start >= 10) step *= 2; + hashes.push(chain[i]); + } + + hashes.push(chain[0]); + + return hashes; +}; + +Chain.prototype.__defineGetter__('orphan_bmap', function() { + var self = this; + return Object.keys(this.orphan.map).reduce(function(out, prev) { + var orphan = self.orphan.map[prev]; + out[orphan.hash('hex')] = orphan; + return out; + }, {}); +}); + Chain.prototype.getOrphanRoot = function getOrphanRoot(hash) { var self = this; @@ -434,14 +504,7 @@ Chain.prototype.getOrphanRoot = function getOrphanRoot(hash) { hash = hash.hash('hex'); } - /* - var orphans = Object.keys(this.orphan.map).reduce(function(out, prev) { - var orphan = self.orphan.map[prev]; - out[orphan.hash('hex')] = orphan; - return out; - }, {}); - */ - var orphans = this.orphan.bmap; + var orphans = this.orphan_bmap; /* var orphanRoot = hash; diff --git a/lib/bcoin/hd.js b/lib/bcoin/hd.js new file mode 100644 index 00000000..8a77237b --- /dev/null +++ b/lib/bcoin/hd.js @@ -0,0 +1,739 @@ +/** + * hd.js - hierarchical determistic seeds and keys (BIP32, BIP39) + * https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki + * https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki + */ + +/** + * Code adapted from bitcore-lib: + * https://github.com/bitpay/bitcore-lib/blob/master/lib/hdprivatekey.js + * https://github.com/bitpay/bitcore-lib/blob/master/lib/hdpublickey.js + * https://github.com/ryanxcharles/fullnode/blob/master/lib/bip32.js + * + * Copyright (c) 2013-2015 BitPay, Inc. + * + * Parts of this software are based on Bitcoin Core + * Copyright (c) 2009-2015 The Bitcoin Core developers + * + * Parts of this software are based on fullnode + * Copyright (c) 2014 Ryan X. Charles + * Copyright (c) 2014 reddit, Inc. + * + * Parts of this software are based on BitcoinJS + * Copyright (c) 2011 Stefan Thomas + * + * Parts of this software are based on BitcoinJ + * Copyright (c) 2011 Google Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +var hd = exports; + +/** + * Modules + */ + +var bcoin = require('../bcoin'); +var hash = require('hash.js'); +var bn = require('bn.js'); +var elliptic = require('elliptic'); +var utils = bcoin.utils; +var assert = utils.assert; + +var EventEmitter = require('events').EventEmitter; +var crypto = require('crypto'); + +var english = require('../../etc/english.json'); + +var ec; +if (!elliptic.curves) { + ec = elliptic.nist.secp256k1; + ec.curve.g = ec.g; + ec.curve.n = ec.n; +} else { + ec = elliptic.curves.secp256k1; +} +var ecPoint = ec.curve.point.bind(ec.curve); +var ecPointFromX = ec.curve.pointFromX.bind(ec.curve); + +/** + * HD Seeds + */ + +function HDSeed(options) { + if (!(this instanceof HDSeed)) { + return new HDSeed(options); + } + this.bits = options.bits || 128; + this.entropy = options.entropy || HDSeed._entropy(this.bits / 8); + this.mnemonic = options.mnemonic || HDSeed._mnemonic(this.entropy); + if (options.passphrase !== undefined) { + this.seed = this.createSeed(options.passphrase); + } +} + +HDSeed.create = function(options) { + var obj = new HDSeed(options); + return obj.seed || obj; +}; + +HDSeed.prototype.createSeed = function(passphrase) { + this.passphrase = passphrase || ''; + return pbkdf2(this.mnemonic, 'mnemonic' + passphrase, 2048, 64); +}; + +HDSeed._entropy = function(size) { + // XXX Do not use crypto + return Array.prototype.slice.call(crypto.randomBytes(size)); +}; + +HDSeed._mnemonic = function(entropy) { + var bin = ''; + for (var i = 0; i < entropy.length; i++) { + bin = bin + ('00000000' + entropy[i].toString(2)).slice(-8); + } + + var mnemonic = []; + for (i = 0; i < bin.length / 11; i++) { + var wi = parseInt(bin.slice(i * 11, (i + 1) * 11), 2); + mnemonic.push(english[wi]); + } + + return mnemonic.join(' '); +}; + +/** + * HD Keys + */ + +var VERSION = { + xpubkey: 0x0488b21e, + xprivkey: 0x0488ade4 +}; + +var HARDENED = 0x80000000; +var MAX_INDEX = 2 * HARDENED; +var MIN_ENTROPY = 128 / 8; +var MAX_ENTROPY = 512 / 8; +var PARENT_FINGER_PRINT_SIZE = 4; +var PATH_ROOTS = ['m', 'M', 'm\'', 'M\'']; + +/** + * HD Private Key + */ + +function HDPriv(options) { + var data; + + if (!(this instanceof HDPriv)) { + return new HDPriv(options); + } + + if (!options) { + options = { seed: new HDSeed({ passphrase: '' }) }; + } + + if (typeof options === 'string' && options.indexOf('xprv') === 0) { + options = { xkey: options }; + } + + if (options.passphrase) { + options.seed = new HDSeed({ passphrase: options.passphrase }); + } + + if (options.seed) { + if (typeof options.seed === 'object' && !(options.seed instanceof HDSeed)) { + options.seed = new HDSeed(options.seed); + } + this.seed = options.seed; + data = this._seed(options.seed); + } else if (options.xkey) { + data = this._unbuild(options.xkey); + } else { + data = options; + } + + data = this._normalize(data, VERSION.xprivkey); + + this.data = data; + + this._build(data); +} + +HDPriv.prototype._normalize = function(data, version) { + var b; + data.version = version || VERSION.xprivkey; + data.version = +data.version; + data.depth = +data.depth; + if (typeof data.parentFingerPrint === 'number') { + b = []; + utils.writeU32BE(b, data.parentFingerPrint, 0); + data.parentFingerPrint = b; + } + data.childIndex = +data.childIndex; + if (typeof data.chainCode === 'string') { + data.chainCode = utils.toArray(data.chainCode, 'hex'); + } + data.privateKey = data.privateKey || data.priv; + if (data.privateKey) { + if (data.privateKey.getPrivate) { + data.privateKey = data.privateKey.getPrivate().toArray(); + } else if (typeof data.privateKey === 'string') { + data.privateKey = utils.toArray(data.privateKey, 'hex'); + } + } + data.publicKey = data.publicKey || data.pub; + if (data.publicKey) { + if (data.publicKey.getPublic) { + data.publicKey = data.privateKey.getPublic(true, 'array'); + } else if (typeof data.publicKey === 'string') { + data.publicKey = utils.toArray(data.publicKey, 'hex'); + } + } + if (typeof data.checksum === 'number') { + b = []; + utils.writeU32BE(b, data.checksum, 0); + data.checksum = b; + } + return data; +}; + +HDPriv.prototype._seed = function(seed) { + if (seed instanceof HDSeed) { + seed = seed.seed; + } + if (typeof seed === 'string' && /^[0-9a-f]+$/i.test(seed)) { + seed = utils.toArray(seed, 'hex'); + } + if (seed.length < MIN_ENTROPY || seed.length > MAX_ENTROPY) { + throw new Error; + } + var hash = sha512hmac(seed, 'Bitcoin seed'); + return { + // version: VERSION.xprivkey, + depth: 0, + parentFingerPrint: 0, + childIndex: 0, + chainCode: hash.slice(32, 64), + privateKey: hash.slice(0, 32), + checksum: null + }; +}; + +HDPriv.prototype._unbuild = function(xkey) { + var raw = utils.fromBase58(xkey); + var data = {}; + var off = 0; + data.version = utils.readU32BE(raw, off); + off += 4; + data.depth = raw[off]; + off += 1; + data.parentFingerPrint = utils.readU32BE(raw, off); + off += 4; + data.childIndex = utils.readU32BE(raw, off); + off += 4; + data.chainCode = raw.slice(off, off + 32); + off += data.chainCode.length; + off += 1; // nul byte + data.privateKey = raw.slice(off, off + 32); + off += data.privateKey.length; + data.checksum = utils.readU32BE(raw, off); + off += 4; + var hash = utils.dsha256(raw.slice(0, -4)); + if (data.checksum !== utils.readU32BE(hash, 0)) { + throw new Error('checksum mismatch'); + } + return data; +}; + +HDPriv.prototype._build = function(data) { + var sequence = []; + var off = 0; + utils.writeU32BE(sequence, data.version, off); + off += 4; + sequence[off] = data.depth; + off += 1; + utils.copy(data.parentFingerPrint, sequence, off, true); + off += data.parentFingerPrint.length; + utils.writeU32BE(sequence, data.childIndex, off); + off += 4; + utils.copy(data.chainCode, sequence, off, true); + off += data.chainCode.length; + sequence[off] = 0; + off += 1; + utils.copy(data.privateKey, sequence, off, true); + off += data.privateKey.length; + var checksum = utils.dsha256(sequence).slice(0, 4); + utils.copy(checksum, sequence, off, true); + off += 4; + + var xprivkey = utils.toBase58(sequence); + + var pair = bcoin.ecdsa.keyPair({ priv: data.privateKey }); + var privateKey = pair.getPrivate().toArray(); + var publicKey = pair.getPublic(true, 'array'); + + var size = PARENT_FINGER_PRINT_SIZE; + var fingerPrint = utils.ripesha(publicKey).slice(0, size); + + this.version = data.version; + this.depth = data.depth; + this.parentFingerPrint = data.parentFingerPrint; + this.childIndex = data.childIndex; + this.chainCode = data.chainCode; + this.privateKey = privateKey; + // this.checksum = checksum; + + this.xprivkey = xprivkey; + this.fingerPrint = fingerPrint; + this.publicKey = publicKey; + + this.hdpub = new HDPub(this); + this.xpubkey = this.hdpub.xpubkey; + this.pair = bcoin.ecdsa.keyPair({ priv: this.privateKey }); +}; + +HDPriv.prototype.derive = function(index, hard) { + if (typeof index === 'string') { + return this.deriveString(index); + } + + hard = index >= HARDENED ? true : hard; + if (index < HARDENED && hard === true) { + index += HARDENED; + } + + var index_ = []; + utils.writeU32BE(index_, index, 0); + + var data; + if (hard) { + data = [0].concat(this.privateKey).concat(index_); + } else { + data = [].concat(this.publicKey).concat(index_); + } + var hash = sha512hmac(data, this.chainCode); + var leftPart = new bn(hash.slice(0, 32)); + var chainCode = hash.slice(32, 64); + + var privateKey = leftPart.add(new bn(this.privateKey)).mod(ec.curve.n).toArray(); + + return new HDPriv({ + // version: this.version, + depth: this.depth + 1, + parentFingerPrint: this.fingerPrint, + childIndex: index, + chainCode: chainCode, + privateKey: privateKey + }); +}; + +// https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki +HDPriv._getIndexes = function(path) { + var steps = path.split('/'); + var root = steps.shift(); + var indexes = []; + + if (~PATH_ROOTS.indexOf(path)) { + return indexes; + } + + if (!~PATH_ROOTS.indexOf(root)) { + return null; + } + + for (var i = 0; i < steps.length; i++) { + var step = steps[i]; + var hard = step[step.length - 1] === '\''; + if (hard) { + step = step.slice(0, -1); + } + if (!step || step[0] === '-') { + return null; + } + var index = +step; // cast to number + if (hard) { + index += HARDENED; + } + indexes.push(index); + } + + return indexes; +}; + +HDPriv.isValidPath = function(path, hard) { + if (typeof path === 'string') { + var indexes = HDPriv._getIndexes(path); + return indexes !== null && indexes.every(HDPriv.isValidPath); + } + + if (typeof path === 'number') { + if (path < HARDENED && hard === true) { + path += HARDENED; + } + return path >= 0 && path < MAX_INDEX; + } + + return false; +}; + +HDPriv.prototype.deriveString = function(path) { + if (!HDPriv.isValidPath(path)) { + throw new Error('invalid path'); + } + + var indexes = HDPriv._getIndexes(path); + + return indexes.reduce(function(prev, index) { + return prev.derive(index); + }); +}; + +/** + * HD Public Key + */ + +function HDPub(options) { + if (!(this instanceof HDPub)) { + return new HDPriv(options); + } + + var data; + + if (typeof options === 'string' && options.indexOf('xpub') === 0) { + options = { xkey: options }; + } + + if (options.xkey) { + data = this._unbuild(options.xkey); + } else { + data = options; + } + + data = this._normalize(data, VERSION.xpubkey); + + this.data = data; + + this._build(data); +} + +HDPub.prototype._normalize = HDPriv.prototype._normalize; + +HDPub.prototype._unbuild = function(xkey) { + var raw = utils.fromBase58(xkey); + var data = {}; + var off = 0; + data.version = utils.readU32BE(raw, off); + off += 4; + data.depth = raw[off]; + off += 1; + data.parentFingerPrint = utils.readU32BE(raw, off); + off += 4; + data.childIndex = utils.readU32BE(raw, off); + off += 4; + data.chainCode = raw.slice(off, off + 32); + off += data.chainCode.length; + data.publicKey = raw.slice(off, off + 33); + off += data.publicKey.length; + data.checksum = utils.readU32BE(raw, off); + off += 4; + var hash = utils.dsha256(raw.slice(0, -4)); + if (data.checksum !== utils.readU32BE(hash, 0)) { + throw new Error('checksum mismatch'); + } + return data; +}; + +HDPub.prototype._build = function(data) { + var sequence = []; + var off = 0; + utils.writeU32BE(sequence, data.version, off); + off += 4; + sequence[off] = data.depth; + off += 1; + utils.copy(data.parentFingerPrint, sequence, off, true); + off += data.parentFingerPrint.length; + utils.writeU32BE(sequence, data.childIndex, off); + off += 4; + utils.copy(data.chainCode, sequence, off, true); + off += data.chainCode.length; + utils.copy(data.publicKey, sequence, off, true); + off += data.publicKey.length; + var checksum = utils.dsha256(sequence).slice(0, 4); + utils.copy(checksum, sequence, off, true); + off += 4; + + if (!data.checksum || !data.checksum.length) { + data.checksum = checksum; + } else if (utils.toHex(checksum) !== utils.toHex(data.checksum)) { + throw new Error; + } + + var xpubkey = utils.toBase58(sequence); + + var publicKey = data.publicKey; + var size = PARENT_FINGER_PRINT_SIZE; + var fingerPrint = utils.ripesha(publicKey).slice(0, size); + + this.version = data.version; + this.depth = data.depth; + this.parentFingerPrint = data.parentFingerPrint; + this.childIndex = data.childIndex; + this.chainCode = data.chainCode; + this.publicKey = publicKey; + // this.checksum = checksum; + + this.xpubkey = xpubkey; + this.fingerPrint = fingerPrint; + + this.xprivkey = data.xprivkey; + this.pair = bcoin.ecdsa.keyPair({ pub: this.publicKey }); +}; + +HDPub.prototype.derive = function(index, hard) { + if (typeof index === 'string') { + return this.deriveString(index); + } + + if (index >= HARDENED || hard) { + throw new Error; + } + if (index < 0) { + throw new Error; + } + + var index_ = []; + utils.writeU32BE(index_, index, 0); + + var data = [].concat(this.publicKey).concat(index_); + var hash = sha512hmac(data, this.chainCode); + var leftPart = new bn(hash.slice(0, 32)); + var chainCode = hash.slice(32, 64); + + var pair = bcoin.ecdsa.keyPair({ pub: this.publicKey }); + var pubkeyPoint = ec.curve.g.mul(leftPart).add(pair.pub); + var publicKey = bcoin.ecdsa.keyFromPublic(pubkeyPoint).getPublic(true, 'array'); + + return new HDPub({ + // version: VERSION.xpubkey, + depth: this.depth + 1, + parentFingerPrint: this.fingerPrint, + childIndex: index, + chainCode: chainCode, + publicKey: publicKey + }); +}; + +HDPub.isValidPath = function(arg) { + if (typeof arg === 'string') { + var indexes = HDPriv._getIndexes(arg); + return indexes !== null && indexes.every(HDPub.isValidPath); + } + if (typeof arg === 'number') { + return arg >= 0 && arg < HARDENED; + } + return false; +}; + +HDPub.prototype.deriveString = function(path) { + if (~path.indexOf('\'')) { + throw new Error('cannot derive hardened'); + } else if (!HDPub.isValidPath(path)) { + throw new Error('invalid path'); + } + + var indexes = HDPriv._getIndexes(path); + + return indexes.reduce(function(prev, index) { + return prev.derive(index); + }); +}; + +/** + * Helpers + */ + +function sha512hmac(data, salt) { + // XXX Do not use crypto + var hmac = crypto.createHmac('sha512', new Buffer(salt)); + var hash = hmac.update(new Buffer(data)).digest(); + return Array.prototype.slice.call(hash); +} + +/** + * PDKBF2 + * Credit to: https://github.com/stayradiated/pbkdf2-sha512 + * Copyright (c) 2014, JP Richardson Copyright (c) 2010-2011 Intalio Pte, All Rights Reserved + */ + +function pbkdf2(key, salt, iterations, dkLen) { + 'use strict'; + + var hLen = 64; + if (dkLen > (Math.pow(2, 32) - 1) * hLen) { + throw Error('Requested key length too long'); + } + + if (typeof key !== 'string' && typeof key.length !== 'number') { + throw new TypeError('key must a string or array'); + } + + if (typeof salt !== 'string' && typeof salt.length !== 'number') { + throw new TypeError('salt must a string or array'); + } + + if (typeof key === 'string') { + key = utils.toArray(key, null); + } + + if (typeof salt === 'string') { + salt = utils.toArray(salt, null); + } + + var DK = new Array(dkLen); + var U = new Array(hLen); + var T = new Array(hLen); + var block1 = new Array(salt.length + 4); + + var l = Math.ceil(dkLen / hLen); + var r = dkLen - (l - 1) * hLen; + + utils.copy(salt.slice(0, salt.length), block1, 0); + for (var i = 1; i <= l; i++) { + block1[salt.length + 0] = (i >> 24 & 0xff); + block1[salt.length + 1] = (i >> 16 & 0xff); + block1[salt.length + 2] = (i >> 8 & 0xff); + block1[salt.length + 3] = (i >> 0 & 0xff); + + U = sha512hmac(block1, key); + + utils.copy(U.slice(0, hLen), T, 0); + + for (var j = 1; j < iterations; j++) { + U = sha512hmac(U, key); + + for (var k = 0; k < hLen; k++) { + T[k] ^= U[k]; + } + } + + var destPos = (i - 1) * hLen; + var len = (i === l ? r : hLen); + utils.copy(T.slice(0, len), DK, 0); + } + + return DK; +} + +/** + * Expose + */ + +hd.seed = HDSeed; +hd.priv = HDPriv; +hd.pub = HDPub; + +/** + * Test + */ + +var phrase = 'volume doll flush federal inflict tomato result property total curtain shield aisle'; +var checkSeed = utils.toHex(pbkdf2(phrase, 'mnemonic' + 'foo', 2048, 64)); +var seed = '5559092716434b83f158bffb51337a944529ae30d7e62d46d3be0c66fa4b36e8d60ccfd2c976b831885dc9df9ac3716ee4bf90003f25621070a49cbea58f528b'; +assert.equal(checkSeed, seed); + +var master_priv = 'xprv9s21ZrQH143K35zTejeVRhkXgegDFUVpoh8Mxs2BQmXEB4w9SZ1CuoJPuQ2KGQrS1ZF3Pk7V7KWHn7FqR2JbAE9Bh8PURnrFnrmArj4kxos'; +var master_pub = 'xpub661MyMwAqRbcFa4vkmBVnqhGEgWhewDgAv3xmFRny74D3sGHz6KTTbcskg2vZEMbEwxc4oaR435oczhSu4GdNwhwiVewcewU8A1Rr6HehAU'; + +var child1_priv = 'xprv9v414VeuxMoGt3t7jzkPni79suCfkgFwjxG38X2wgfg2mrYtV4Bhj3prhDDCcBiJrz9n4xLYoDtBFRuQmreVLKzmiZAqvbGpx5q4yHfzfah'; +var child1_pub = 'xpub693MU1BonjMa6Xxar2HQ9r3tRw3AA8yo7BBdvuSZF1D1eet32bVxGr9LYViWMtaLfQaa2StXeUmDG5VELFkU9pc3yfTzCk61WQJdR6ezj7a'; + +var child2_pub = 'xpub693MU1BonjMa8MMoz9opJhrFejcXcGmhMP9gzySLsip4Dz1UrSLT4i2pAimHDyM2onW2H2L2HkbwrZqoizQLMoErXu8mPYxDf8tJUBAfBuT'; + +var child3_priv = 'xprv9v414VeuxMoGusHLt8GowZuX6hn3Cp3qzAE6Cb2jKPH5MBgLJu2CWuiLKTdWV8WoNFYvpCcBfbpWfeyEQ8zytZW5qy39roTcugBGUkeAvCc'; +var child3_pub = 'xpub693MU1BonjMa8MMoz9opJhrFejcXcGmhMP9gzySLsip4Dz1UrSLT4i2pAimHDyM2onW2H2L2HkbwrZqoizQLMoErXu8mPYxDf8tJUBAfBuT'; + +var child4_priv = 'xprv9v414VeuxMoGyViVYuzEN5vLDzff3nkrH5Bf4KzD1iTeY855Q4cCc6xPPNoc6MJcsqqRQiGqR977cEEGK2mhVp7ALKHqY1icEw3Q9UmfQ1v'; +var child4_pub = 'xpub693MU1BonjMaBynxewXEjDs4n2W9TFUheJ7FriPpa3zdQvQDwbvT9uGsEebvioAcYbtRUU7ge4yVgj8WDLrwtwuXKTWiieFoYX2B1JYUEfK'; + +var child5_priv = 'xprv9xaK29Nm86ytEwsV9YSsL3jWYR6KtZYY3cKdjAbxHrwKyxH9YWoxvqKwtgQmExGpxAEDrwB4WK9YG1iukth3XiSgsxXLK1W3NB31gLee8fi'; +var child5_pub = 'xpub6BZfReuexUYBTRwxFZyshBgF6SvpJ2GPQqFEXZ1ZrCUJrkcJ648DUdeRjx9QiNQxQvPcHYV3rGkvuExFQbVRS4kU5ynx4fAsWWhHgyPh1pP'; + +var child6_priv = 'xprv9xaK29Nm86ytGx9uDhNKUBjvbJ1sAEM11aYxGQS66Rmg6oHwy7HbB6kWwMHvukzdbPpGhfNXhZgePWFHm1DCh5PACPFywJJKr1AnUJTLjUc'; +var child6_pub = 'xpub6BZfReuexUYBVSENKiuKqKgf9KrMZh4rNoUZ4nqhemJeybd6Webqiu4zndBwa9UB4Jvr5jB5Bcgng6reXAKCuDiVm7zhzJ13BUDBiM8HidZ'; + +var master = hd.priv({ seed: seed }); +var child1 = master.derive(0); +var child2 = master.hdpub.derive(1); +var child3 = master.derive(1); +var child4 = master.derive(2); +var child5 = child4.derive(0); +var child6 = child4.derive(1); +master._unbuild(master.xprivkey); +master.hdpub._unbuild(master.hdpub.xpubkey); + +// console.log(new HDSeed({ +// // I have the same combination on my luggage: +// entropy: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], +// passphrase: 'foo' +// })); + +try { + assert.equal(master.xprivkey, master_priv); + assert.equal(master.xpubkey, master_pub); + + assert.equal(child1.xprivkey, child1_priv); + assert.equal(child1.xpubkey, child1_pub); + + assert.equal(child2.xpubkey, child2_pub); + + assert.equal(child3.xprivkey, child3_priv); + assert.equal(child3.xpubkey, child3_pub); + + assert.equal(child4.xprivkey, child4_priv); + assert.equal(child4.xpubkey, child4_pub); + + assert.equal(child5.xprivkey, child5_priv); + assert.equal(child5.xpubkey, child5_pub); + + assert.equal(child6.xprivkey, child6_priv); + assert.equal(child6.xpubkey, child6_pub); +} catch (e) { + console.log('master xprivkey: %s', master.xprivkey); + console.log('master xpubkey: %s', master.xpubkey); + console.log('-'); + console.log('child1 xprivkey: %s', child1.xprivkey); + console.log('child1 xpubkey: %s', child1.xpubkey); + console.log('-'); + console.log('child2 xpubkey: %s', child2.xpubkey); + console.log('-'); + console.log('child3 xprivkey: %s', child3.xprivkey); + console.log('child3 xpubkey: %s', child3.xpubkey); + console.log('-'); + console.log('child3 xprivkey: %s', child4.xprivkey); + console.log('child3 xpubkey: %s', child4.xpubkey); + console.log('-'); + console.log('child5 xprivkey: %s', child5.xprivkey); + console.log('child5 xpubkey: %s', child5.xpubkey); + console.log('-'); + console.log('child6 xprivkey: %s', child6.xprivkey); + console.log('child6 xpubkey: %s', child6.xpubkey); + throw e; +} diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index e2e7fb9b..a1c87aa0 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -94,8 +94,7 @@ Peer.prototype._init = function init() { var ip = self.socket && self.socket.remoteAddress || '0.0.0.0'; self.pool.emit('debug', 'version (%s): loading locator hashes for getblocks', ip); self.chain.on('load', function() { - // self.loadBlocks(self.pool.locatorHashes(), 0); - self.loadHeaders(self.pool.locatorHashes(), 0); + self.loadHeaders(self.chain.locatorHashes(), 0); }); }); } @@ -438,61 +437,6 @@ Peer.prototype._handleInv = function handleInv(items) { }); this.emit('blocks', blocks); - if (0) - if (this.pool.options.fullNode) { - var self = this; - - if (txs.length) { - this.emit('txs', txs.map(function(tx) { - return tx.hash; - })); - this.getData(txs); - } - - if (blocks.length === 1) { - this._rChainHead = utils.toHex(blocks[0]); - } - - var orph = blocks.filter(function(block) { - return self._requestingOrphan === utils.toHex(block); - }); - - if (orph.length) { - utils.debug('FOUND MISSING BLOCK'); - utils.debug('FOUND MISSING BLOCK'); - utils.debug('FOUND MISSING BLOCK'); - } - - /* - var orphans = Object.keys(self.chain.orphan.map).reduce(function(out, prev) { - var orphan = self.chain.orphan.map[prev]; - out[orphan.hash('hex')] = orphan; - return out; - }, {}); - */ - - var orphans = self.chain.orphan.bmap; - - for (var i = 0; i < blocks.length; i++) { - var hash = utils.toHex(blocks[i]); - if (orphans[hash]) { - this.loadBlocks(this.pool.locatorHashes(), this.chain.getOrphanRoot(hash)); - continue; - } - if (!this.chain.index.bloom.test(hash, 'hex')) { - this.getData([{ type: 'block', hash: hash }]); - } - } - -/* - this.getData(blocks.map(function(block) { - return { type: 'block', hash: utils.toHex(block) }; - })); -*/ - - return; - } - if (txs.length === 0) return; @@ -504,36 +448,30 @@ Peer.prototype._handleInv = function handleInv(items) { Peer.prototype._handleHeaders = function handleHeaders(headers) { var self = this; + headers = headers.map(function(header) { header.prevBlock = utils.toHex(header.prevBlock); header.merkleRoot = utils.toHex(header.merkleRoot); - var abbr = bcoin.block.prototype.abbr.call(header); - header._hash = utils.toHex(utils.dsha256(abbr)); + header.hash = utils.toHex(utils.dsha256(header._raw)); return header; }); if (this.pool.options.fullNode) { - var self = this; - - if (headers.length === 1) { - this._rChainHead = headers[0]._hash; - } - - var orphans = self.chain.orphan.bmap; - for (var i = 0; i < headers.length; i++) { - var hash = headers[i]._hash; - if (orphans[hash]) { - this.loadHeaders(this.pool.locatorHashes(), this.chain.getOrphanRoot(hash)); + var header = headers[i]; + var hash = header.hash; + // if (this.chain.orphan_bmap[hash]) { + if (this.chain.orphan.map[header.prevBlock]) { + this.loadHeaders(this.chain.locatorHashes(), this.chain.getOrphanRoot(hash)); continue; } - if (!this.chain.index.bloom.test(hash, 'hex')) { + if (!this.chain.index.bloom.test(hash, 'hex') || i === headers.length - 1) { this.getData([{ type: 'block', hash: hash }]); } } - - return; } + + this.emit('headers', headers); }; diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index d09d85bd..b56b89ba 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -327,25 +327,21 @@ Pool.prototype._addPeer = function _addPeer(backoff) { peer.on('block', function(block) { backoff = 0; - var hash = block.hash('hex'); - if (hash === peer._requestingOrphan) { - delete peer._requestingOrphan; - } var height = self.chain.index.hashes.length; + var hash = block.hash('hex'); + + if (!self.chain.index.bloom.test(block.prevBlock, 'hex')) { + peer.loadHeaders(self.chain.locatorHashes(), self.chain.getOrphanRoot(block)); + } - // Otherwise, accept block self._response(block); self.chain.add(block); - if (self.chain.orphan.map[block.prevBlock] || !self.chain.index.bloom.test(block.prevBlock)) { - // if (!self.chain.index.bloom.test(block.prevBlock)) { - if (peer._requestingOrphan) return; - var orphanRoot = self.chain.getOrphanRoot(block); - peer._requestingOrphan = orphanRoot; - peer.loadHeaders(self.locatorHashes(), orphanRoot); - } else if (self.chain.index.hashes.length === height) { - return; - } + // if (!self.chain.index.bloom.test(block.prevBlock, 'hex')) { + // peer.loadHeaders(self.chain.locatorHashes(), self.chain.getOrphanRoot(block)); + // } else if (self.chain.index.hashes.length === height) { + // return; + // } self.emit('chain-progress', self.chain.fillPercent(), peer); self.emit('block', block, peer); @@ -391,68 +387,6 @@ Pool.prototype._addPeer = function _addPeer(backoff) { }); }; -Pool.prototype.locatorHashes = function(index) { - var chain = this.chain.index.hashes; - var hashes = []; - var top = chain.length - 1; - var step = 1; - var i; - - if (typeof index === 'string') { - for (i = top; i >= 0; i--) { - if (chain[i] === index) { - top = i; - break; - } - } - } else if (typeof index === 'number') { - top = index; - } - - i = top; - for (;;) { - hashes.push(chain[i]); - i = i - step; - if (i <= 0) { - hashes.push(chain[0]); - break; - } - if (hashes.length >= 10) { - step *= 2; - } - } - - return hashes; -}; - -Pool.prototype.locatorHashes = function(index) { - var self = this; - var hashes = this.chain.index.hashes; - var indicies = []; - var top = hashes.length - 1; - var step = 1, start = 0; - - if (typeof index === 'string') { - for (i = top; i >= 0; i--) { - if (chain[i] === index) { - top = i; - break; - } - } - } else if (typeof index === 'number') { - top = index; - } - - for (var i = top; i > 0; i -= step, ++start) { - if (start >= 10) step *= 2; - indicies.push(hashes[i]); - } - - indicies.push(hashes[0]); - - return indicies; -}; - Pool.prototype._removePeer = function _removePeer(peer) { var i = this.peers.pending.indexOf(peer); if (i !== -1) diff --git a/lib/bcoin/protocol/constants.js b/lib/bcoin/protocol/constants.js index 0842b379..b3ce3946 100644 --- a/lib/bcoin/protocol/constants.js +++ b/lib/bcoin/protocol/constants.js @@ -19,6 +19,12 @@ exports.genesis = { nonce: 2083236893 }; +// address versions +exports.addr = { + normal: 0, + p2sh: 5 +}; + // version - services field exports.services = { network: 1 @@ -135,6 +141,9 @@ exports.opcodes = { for (var i = 1; i <= 16; i++) exports.opcodes[i] = 0x50 + i; +exports.opcodes['false'] = exports.opcodes['0']; +exports.opcodes['true'] = exports.opcodes['1']; + exports.opcodesByVal = new Array(256); Object.keys(exports.opcodes).forEach(function(name) { exports.opcodesByVal[exports.opcodes[name]] = name; diff --git a/lib/bcoin/protocol/parser.js b/lib/bcoin/protocol/parser.js index 39ecf24d..a5fd849d 100644 --- a/lib/bcoin/protocol/parser.js +++ b/lib/bcoin/protocol/parser.js @@ -237,6 +237,7 @@ Parser.prototype.parseHeaders = function parseHeaders(p) { if (p.length >= off + 81) { for (var i = 0; i < count && off + 81 < p.length; i++) { var header = {}; + var start = off; header.version = readU32(p, off); off += 4; header.prevBlock = p.slice(off, off + 32); @@ -252,6 +253,7 @@ Parser.prototype.parseHeaders = function parseHeaders(p) { var r = readIntv(p, off); header.totalTX = r.r; off = r.off; + header._raw = p.slice(start, start + 80); headers.push(header); } } diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 1578196f..cfd558f3 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -62,6 +62,7 @@ script.encode = function encode(s) { // Push value to stack if (Array.isArray(instr)) { if (instr.length === 0) { + // OP_FALSE res.push(0); } else if (1 <= instr.length && instr.length <= 0x4b) { res = res.concat(instr.length, instr); diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 970784d0..0db08f46 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -128,17 +128,45 @@ TX.prototype.out = function out(output, value) { var script = output.script ? output.script.slice() : []; - // Multisig script if given addresses if (Array.isArray(output.keys || output.address)) { + // Raw multisig transaction + // https://github.com/bitcoin/bips/blob/master/bip-0010.mediawiki + // https://github.com/bitcoin/bips/blob/master/bip-0011.mediawiki + // https://github.com/bitcoin/bips/blob/master/bip-0019.mediawiki + // [required-sigs] [pubkey-hash1] [pubkey-hash2] ... [number-of-keys] checkmultisig var keys = output.keys || output.address; + if (keys === output.address) { + keys = keys.map(function(address) { + return bcoin.wallet.addr2hash(address); + }); + } + keys = keys.map(function(key) { + if (typeof key === 'string') { + return utils.toKeyArray(key); + } + return key; + }); script = [ [ output.minSignatures || keys.length ] ].concat( keys, [ [ keys.length ], 'checkmultisig' ] ); - // Default script if given address + // outputs: [ [ 2 ], 'key1', 'key2', [ 2 ], 'checkmultisig' ] + // in reality: + // outputs: [ [ 2 ], [0,1,...], [2,3,...], [ 2 ], 'checkmultisig' ] + } else if (bcoin.wallet.validAddress(output.address, 'p2sh')) { + // p2sh transaction + // https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki + // hash160 [20-byte-redeemscript-hash] equal + script = [ + 'hash160', + bcoin.wallet.addr2hash(output.address, 'p2sh'), + 'eq' + ]; } else if (output.address) { + // p2pkh transaction + // dup hash160 [pubkey-hash] equalverify checksig script = [ 'dup', 'hash160', @@ -207,6 +235,23 @@ TX.prototype.verify = function verify(index, force) { }, this); }; +TX.prototype.signature = function signature(tx, key, index, sighash) { + if (typeof index !== 'number') { + index = tx.inputs.indexOf(index); + if (index === -1) return; + } + var input = tx.inputs[index]; + var subscript = input.out.tx.getSubscript(input.out.index); + var hash = tx.subscriptHash(input, subscript, sighash); + var signature = bcoin.ecdsa.sign(hash, key).toDER(); + signature = signature.concat(bcoin.protocol.constants.hashType[sighash]); + return signature; +}; + +TX.prototype.isCoinbase = function isCoinbase() { + return this.inputs.length === 1 && +this.inputs[0].out.hash === 0; +}; + TX.prototype.maxSize = function maxSize() { // Create copy with 0-script inputs var copy = this.clone(); @@ -300,7 +345,3 @@ TX.fromJSON = function fromJSON(json) { return tx; }; - -TX.prototype.clone = function clone() { - return new TX(new bcoin.protocol.parser().parseTX(this.render())); -}; diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index 748193c1..faa13036 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -467,6 +467,20 @@ utils.isIP = function(ip) { return 0; }; +utils.isHex = function(msg) { + return typeof msg === 'string' && /^[0-9a-f]+$/i.test(msg); +}; + +utils.toKeyArray = function(msg) { + if (typeof msg !== 'string') + return msg; + + if (utils.isHex(msg)) + return utils.toArray(msg, 'hex'); + + return utils.fromBase58(msg); +}; + utils.debug = function(msg) { console.log('\x1b[31m' + msg + '\x1b[m'); }; diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 65b64919..bdce2416 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -5,6 +5,7 @@ var inherits = require('inherits'); var EventEmitter = require('events').EventEmitter; var utils = bcoin.utils; var assert = utils.assert; +var constants = bcoin.protocol.constants; function Wallet(options, passphrase) { if (!(this instanceof Wallet)) @@ -28,8 +29,18 @@ function Wallet(options, passphrase) { this.key = null; this.loaded = false; this.lastTs = 0; + this.publicKeys = options.publicKeys; - if (options.passphrase) { + if (options.priv instanceof bcoin.hd.priv) { + this.hd = options.priv; + this.key = this.hd.pair; + } else if (options.pub instanceof bcoin.hd.pub) { + this.hd = options.pub; + this.key = this.hd.pair; + } else if (options.hd) { + this.hd = bcoin.hd.priv(options); + this.key = this.hd.pair; + } else if (options.passphrase) { this.key = bcoin.ecdsa.genKeyPair({ pers: options.scope, entropy: hash.sha256().update(options.passphrase).digest() @@ -47,6 +58,26 @@ function Wallet(options, passphrase) { this.fee = 10000; this.dust = 5460; + if (options.m != null) { + this.m = options.m; + this.n = options.n; + this.publicKeys = options.publicKeys || []; + + if (this.n < this.publicKeys.length) { + this.publicKeys.push(this.getPublicKey('base58')); + } + + if (this.m < 1 || this.m > this.n) { + throw new Error('m ranges between 1 and n'); + } + if (this.n < 1 || this.n > 7) { + throw new Error('n ranges between 1 and 7'); + } + if (this.publicKeys.length !== this.n) { + throw new Error(this.n + ' public keys required'); + } + } + this._init(); } inherits(Wallet, EventEmitter); @@ -114,6 +145,8 @@ Wallet.prototype.getPublicKey = function getPublicKey(enc) { var pub = this.key.getPublic(this.compressed, 'array'); if (enc === 'base58') return utils.toBase58(pub); + else if (enc === 'hex') + return utils.toHex(pub); else return pub; }; @@ -126,23 +159,25 @@ Wallet.prototype.getAddress = function getAddress() { return Wallet.hash2addr(this.getHash()); }; -Wallet.hash2addr = function hash2addr(hash) { +Wallet.hash2addr = function hash2addr(hash, version) { hash = utils.toArray(hash, 'hex'); - // Add version - hash = [ 0 ].concat(hash); + version = constants.addr[version || 'normal']; + hash = [ version ].concat(hash); var addr = hash.concat(utils.checksum(hash)); return utils.toBase58(addr); }; -Wallet.addr2hash = function addr2hash(addr) { +Wallet.addr2hash = function addr2hash(addr, version) { if (!Array.isArray(addr)) addr = utils.fromBase58(addr); + version = constants.addr[version || 'normal']; + if (addr.length !== 25) return []; - if (addr[0] !== 0) + if (addr[0] !== version) return []; var chk = utils.checksum(addr.slice(0, -4)); @@ -152,6 +187,13 @@ Wallet.addr2hash = function addr2hash(addr) { return addr.slice(1, -4); }; +Wallet.validAddress = function validAddr(addr, version) { + if (!addr) + return false; + + return !!Wallet.addr2hash(addr, version).length; +}; + Wallet.prototype.validateAddress = function validateAddress(addr) { var p = Wallet.addr2hash(addr); return p.length !== 0; @@ -161,6 +203,7 @@ Wallet.validateAddress = Wallet.prototype.validateAddress; Wallet.prototype.ownOutput = function ownOutput(tx, index) { var hash = this.getHash(); var key = this.getPublicKey(); + var outputs = tx.outputs.filter(function(output, i) { if (index !== undefined && index !== i) return false; @@ -176,6 +219,11 @@ Wallet.prototype.ownOutput = function ownOutput(tx, index) { if (bcoin.script.isMultisig(s, key)) return true; + if (bcoin.script.isScripthash(s) + && utils.isEqual(s[1], this.getP2SHHash())) { + return true; + } + return false; }, this); if (outputs.length === 0) @@ -206,6 +254,11 @@ Wallet.prototype.ownInput = function ownInput(tx, index) { if (bcoin.script.isMultisig(s, key)) return true; + if (bcoin.script.isScripthash(s) + && utils.isEqual(s[1], this.getP2SHHash())) { + return true; + } + return false; }, this); if (inputs.length === 0) @@ -217,7 +270,8 @@ Wallet.prototype.ownInput = function ownInput(tx, index) { Wallet.prototype.sign = function sign(tx, type, inputs, off) { if (!type) type = 'all'; - assert.equal(type, 'all'); + + // assert.equal(type, 'all'); if (!off) off = 0; @@ -236,13 +290,31 @@ Wallet.prototype.sign = function sign(tx, type, inputs, off) { var signature = bcoin.ecdsa.sign(hash, this.key).toDER(); signature = signature.concat(bcoin.protocol.constants.hashType[type]); - if (bcoin.script.isPubkeyhash(s)) { + if (bcoin.script.isPubkeyhash(s) || bcoin.script.isSimplePubkeyhash(s)) { input.script = [ signature, pub ]; return true; } // Multisig - input.script = [ [], signature ]; + // empty array == OP_FALSE == OP_0 + // raw format: OP_FALSE [sig-1] [sig-2] ... + // p2sh format: OP_FALSE [sig-1] [sig-2] ... [redeem-script] + + if (bcoin.script.isMultisig(s) || bcoin.script.isScripthash(s)) { + // XXX Check own? + // || (bcoin.script.isScripthash(s) && utils.isEqual(s[1], this.getP2SHHash())) { + if (!input.script || !input.script.length) { + input.script = [ [], signature ]; + } else if (!~input.script.indexOf(signature)) { + input.script.push(signature); + } + } + + if (bcoin.script.isScripthash(s)) { + if (input.script.length - 1 === this.n) { + input.script.push(this.getP2SHRedemption()); + } + } return true; }, this); @@ -391,6 +463,50 @@ Wallet.prototype.getChange = function fill(tx) { return this.fill(tx, { _getChange: true }); }; +/** + * P2SH (and Multisig) + */ + +Wallet.prototype.getP2SHHash = function() { + return this.getP2SH().hash; +}; + +Wallet.prototype.getP2SHAddress = function() { + return this.getP2SH().address; +}; + +Wallet.prototype.getP2SHRedemption = function() { + return this.getP2SH().redemption; +}; + +Wallet.prototype.getP2SH = function(redeem) { + this.publicKeys = this.publicKeys.map(function(key) { + return utils.toKeyArray(key); + }); + var redemption = redeem || this._createMultisigRedemption(); + var hash = utils.ripasha(redemption); + return { + hash: hash, + address: bcoin.wallet.hash2addr(hash, 'p2sh'), + redemption: redemption + }; +}; + +Wallet.prototype._createMultisigRedemption = function() { + var publicKeys = this.publicKeys; + var mcode = constants.opcodes['1'] + (this.m - 1); + var ncode = script.push(constants.opcodes['1'] + (this.n - 1)); + var redemption = []; + redemption.push(mcode); + this.publicKeys.forEach(function(pubkey) { + redemption.push(pubkey.length); + redemption = redemption.concat(pubkey); + }, this); + redemption.push(ncode); + redemption.push(constants.opcodes.checkmultisig); + return redemption; +}; + Wallet.prototype.toJSON = function toJSON() { return { v: 1, From 87eea1e398d5f92b894a73ec5afee8321c27bc46 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 4 Dec 2015 22:50:13 -0800 Subject: [PATCH 06/59] fixes and minor style changes --- lib/bcoin/peer.js | 2 +- lib/bcoin/protocol/framer.js | 2 +- lib/bcoin/tx.js | 6 +++--- lib/bcoin/wallet.js | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index a1c87aa0..8bb9d431 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -475,7 +475,7 @@ Peer.prototype._handleHeaders = function handleHeaders(headers) { }; -Peer.prototype.loadHeaders = function loadBlocks(hashes, stop) { +Peer.prototype.loadHeaders = function loadHeaders(hashes, stop) { this._write(this.framer.getHeaders(hashes, stop)); }; diff --git a/lib/bcoin/protocol/framer.js b/lib/bcoin/protocol/framer.js index ce6ea7c0..7dfbb213 100644 --- a/lib/bcoin/protocol/framer.js +++ b/lib/bcoin/protocol/framer.js @@ -201,7 +201,7 @@ Framer.prototype.filterClear = function filterClear() { return this.packet('filterclear', []); }; -Framer.prototype.getHeaders = function getBlocks(hashes, stop) { +Framer.prototype.getHeaders = function getHeaders(hashes, stop) { return this._getBlocks('getheaders', hashes, stop); }; diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 0db08f46..198ed593 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -235,14 +235,14 @@ TX.prototype.verify = function verify(index, force) { }, this); }; -TX.prototype.signature = function signature(tx, key, index, sighash) { +TX.prototype.signature = function signature(key, index, sighash) { if (typeof index !== 'number') { index = tx.inputs.indexOf(index); if (index === -1) return; } - var input = tx.inputs[index]; + var input = this.inputs[index]; var subscript = input.out.tx.getSubscript(input.out.index); - var hash = tx.subscriptHash(input, subscript, sighash); + var hash = this.subscriptHash(input, subscript, sighash); var signature = bcoin.ecdsa.sign(hash, key).toDER(); signature = signature.concat(bcoin.protocol.constants.hashType[sighash]); return signature; diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index bdce2416..1cc8de97 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -487,7 +487,7 @@ Wallet.prototype.getP2SH = function(redeem) { var hash = utils.ripasha(redemption); return { hash: hash, - address: bcoin.wallet.hash2addr(hash, 'p2sh'), + address: Wallet.hash2addr(hash, 'p2sh'), redemption: redemption }; }; @@ -495,7 +495,7 @@ Wallet.prototype.getP2SH = function(redeem) { Wallet.prototype._createMultisigRedemption = function() { var publicKeys = this.publicKeys; var mcode = constants.opcodes['1'] + (this.m - 1); - var ncode = script.push(constants.opcodes['1'] + (this.n - 1)); + var ncode = constants.opcodes['1'] + (this.n - 1); var redemption = []; redemption.push(mcode); this.publicKeys.forEach(function(pubkey) { From c8e04d855292171f20398509c9188f44fe8ca30e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 4 Dec 2015 23:16:02 -0800 Subject: [PATCH 07/59] comments --- lib/bcoin/wallet.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 1cc8de97..6f4d0faf 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -290,6 +290,7 @@ Wallet.prototype.sign = function sign(tx, type, inputs, off) { var signature = bcoin.ecdsa.sign(hash, this.key).toDER(); signature = signature.concat(bcoin.protocol.constants.hashType[type]); + // P2PKH and simple tx if (bcoin.script.isPubkeyhash(s) || bcoin.script.isSimplePubkeyhash(s)) { input.script = [ signature, pub ]; return true; @@ -299,7 +300,6 @@ Wallet.prototype.sign = function sign(tx, type, inputs, off) { // empty array == OP_FALSE == OP_0 // raw format: OP_FALSE [sig-1] [sig-2] ... // p2sh format: OP_FALSE [sig-1] [sig-2] ... [redeem-script] - if (bcoin.script.isMultisig(s) || bcoin.script.isScripthash(s)) { // XXX Check own? // || (bcoin.script.isScripthash(s) && utils.isEqual(s[1], this.getP2SHHash())) { @@ -310,6 +310,7 @@ Wallet.prototype.sign = function sign(tx, type, inputs, off) { } } + // P2SH requires a redeem script after signatures if (bcoin.script.isScripthash(s)) { if (input.script.length - 1 === this.n) { input.script.push(this.getP2SHRedemption()); @@ -325,7 +326,8 @@ Wallet.prototype.sign = function sign(tx, type, inputs, off) { Wallet.prototype.signEmpty = function sign(tx, type, inputs, off) { if (!type) type = 'all'; - assert.equal(type, 'all'); + + // assert.equal(type, 'all'); if (!off) off = 0; From acbe4d0c2e471f6501084035a602573b551be91a Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 5 Dec 2015 00:01:59 -0800 Subject: [PATCH 08/59] remove wallet.signEmpty and tx.signature for now. --- lib/bcoin/tx.js | 13 ------------- lib/bcoin/wallet.js | 30 ------------------------------ 2 files changed, 43 deletions(-) diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 198ed593..ff56709d 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -235,19 +235,6 @@ TX.prototype.verify = function verify(index, force) { }, this); }; -TX.prototype.signature = function signature(key, index, sighash) { - if (typeof index !== 'number') { - index = tx.inputs.indexOf(index); - if (index === -1) return; - } - var input = this.inputs[index]; - var subscript = input.out.tx.getSubscript(input.out.index); - var hash = this.subscriptHash(input, subscript, sighash); - var signature = bcoin.ecdsa.sign(hash, key).toDER(); - signature = signature.concat(bcoin.protocol.constants.hashType[sighash]); - return signature; -}; - TX.prototype.isCoinbase = function isCoinbase() { return this.inputs.length === 1 && +this.inputs[0].out.hash === 0; }; diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 6f4d0faf..ffab83e2 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -271,8 +271,6 @@ Wallet.prototype.sign = function sign(tx, type, inputs, off) { if (!type) type = 'all'; - // assert.equal(type, 'all'); - if (!off) off = 0; @@ -323,34 +321,6 @@ Wallet.prototype.sign = function sign(tx, type, inputs, off) { return inputs.length; }; -Wallet.prototype.signEmpty = function sign(tx, type, inputs, off) { - if (!type) - type = 'all'; - - // assert.equal(type, 'all'); - - if (!off) - off = 0; - - var pub = this.getPublicKey(); - inputs = inputs || tx.inputs; - - // Add signature script to each input - inputs = inputs.filter(function(input, i) { - // Filter inputs that this wallet own - if (!input.out.tx || !this.ownOutput(input.out.tx)) - return false; - - var signature = [0x30, 0, 0x02, 0, 0, 0x02, 0, 0]; - signature = signature.concat(bcoin.protocol.constants.hashType[type]); - - input.script = [ signature, pub ]; - return true; - }, this); - - return inputs.length; -}; - Wallet.prototype.addTX = function addTX(tx, block) { return this.tx.add(tx); }; From 1d210589c55fbe37bbb189607f8301280734973e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 5 Dec 2015 00:02:23 -0800 Subject: [PATCH 09/59] comments explaining signing logic. --- lib/bcoin/wallet.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index ffab83e2..063b0194 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -283,9 +283,18 @@ Wallet.prototype.sign = function sign(tx, type, inputs, off) { if (!input.out.tx || !this.ownOutput(input.out.tx)) return false; + // Get the previous output's subscript var s = input.out.tx.getSubscript(input.out.index); + + // Get the hash of the current tx, minus the other inputs, plus the sighash. + // `off` is used here in a case where we have multiple wallet objects + // signing the same tx. var hash = tx.subscriptHash(off + i, s, type); + + // Sign the transaction with our one input var signature = bcoin.ecdsa.sign(hash, this.key).toDER(); + + // Add the sighash as a single byte to the signature signature = signature.concat(bcoin.protocol.constants.hashType[type]); // P2PKH and simple tx From 1e10b83acf39e018e85efe72bdb3419e447edac9 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 5 Dec 2015 00:50:19 -0800 Subject: [PATCH 10/59] fix redeem script push in wallet. --- lib/bcoin/wallet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 063b0194..c2be1abe 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -319,7 +319,7 @@ Wallet.prototype.sign = function sign(tx, type, inputs, off) { // P2SH requires a redeem script after signatures if (bcoin.script.isScripthash(s)) { - if (input.script.length - 1 === this.n) { + if (input.script.length - 1 === this.m) { input.script.push(this.getP2SHRedemption()); } } From 377e15687403a7697170d92071331677c5bdeb32 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 5 Dec 2015 01:03:51 -0800 Subject: [PATCH 11/59] fix m, n, publicKeys in wallet constructor. --- lib/bcoin/wallet.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index c2be1abe..0fd52154 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -58,13 +58,13 @@ function Wallet(options, passphrase) { this.fee = 10000; this.dust = 5460; - if (options.m != null) { - this.m = options.m; - this.n = options.n; - this.publicKeys = options.publicKeys || []; + this.m = options.m || 1; + this.n = options.n || 1; + this.publicKeys = options.publicKeys; - if (this.n < this.publicKeys.length) { - this.publicKeys.push(this.getPublicKey('base58')); + if (this.publicKeys) { + if (this.publicKeys.length < this.n) { + this.publicKeys.push(this.getPublicKey()); } if (this.m < 1 || this.m > this.n) { From 505aad872971ca1629f2372c14b76326fd256562 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 5 Dec 2015 03:01:37 -0800 Subject: [PATCH 12/59] improve/fix p2sh. move input signing functionality to tx object. --- lib/bcoin/protocol/constants.js | 7 ++ lib/bcoin/script.js | 43 +++++++-- lib/bcoin/tx.js | 110 ++++++++++++++++++++++- lib/bcoin/wallet.js | 153 ++++++++++++++++---------------- 4 files changed, 229 insertions(+), 84 deletions(-) diff --git a/lib/bcoin/protocol/constants.js b/lib/bcoin/protocol/constants.js index b3ce3946..f52804e7 100644 --- a/lib/bcoin/protocol/constants.js +++ b/lib/bcoin/protocol/constants.js @@ -22,6 +22,8 @@ exports.genesis = { // address versions exports.addr = { normal: 0, + p2pkh: 0, + multisig: 0, p2sh: 5 }; @@ -157,6 +159,11 @@ exports.hashType = { anyonecaypay: 0x80 }; +exports.rhashType = Object.keys(exports.hashType).reduce(function(out, type) { + out[exports.hashType[type]] = type; + return out; +}, {}); + exports.block = { maxSize: 1000000, maxSigops: 1000000 / 50, diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index cfd558f3..166dad63 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -151,6 +151,10 @@ script.execute = function execute(s, stack, tx) { if (type !== 1) return false; + // XXX Deal with different hashTypes besides `all` + // if (typeof tx === 'function') + // tx = tx(constants.rhashType[type]); + var res = bcoin.ecdsa.verify(tx, sig.slice(0, -1), pub); if (o === 'checksigverify') { if (!res) @@ -195,6 +199,10 @@ script.execute = function execute(s, stack, tx) { if (type !== 1) return false; + // XXX Deal with different hashTypes besides `all` + // if (typeof tx === 'function') + // tx = tx(constants.rhashType[type]); + var res = false; for (; !res && j < n; j++) res = bcoin.ecdsa.verify(tx, sig.slice(0, -1), keys[j]); @@ -221,6 +229,23 @@ script.execute = function execute(s, stack, tx) { return true; }; +script.redemption = function(publicKeys, m, n) { + if (publicKeys.length !== m) { + throw new Error('wrong amount of pubkeys for redeem script'); + } + var mcode = constants.opcodes['1'] + (m - 1); + var ncode = constants.opcodes['1'] + (n - 1); + var redemption = []; + redemption.push(mcode); + publicKeys.forEach(function(pubkey) { + redemption.push(pubkey.length); + redemption = redemption.concat(pubkey); + }); + redemption.push(ncode); + redemption.push(constants.opcodes.checkmultisig); + return redemption; +}; + script.isPubkeyhash = function isPubkeyhash(s, hash) { if (s.length !== 5) return false; @@ -296,14 +321,22 @@ script.isPubkeyhashInput = function isPubkeyhashInput(s) { 33 <= s[1].length && s[1].length <= 65; }; -script.isScripthash = function isScripthash(s) { +script.isScripthash = function isScripthash(s, hash) { if (s.length !== 3) return false; - return s[0] === 'hash160' && - Array.isArray(s[1]) && - s[1].length === 20 && - s[2] === 'eq'; + var ret = s[0] === 'hash160' && + Array.isArray(s[1]) && + s[1].length === 20 && + s[2] === 'eq'; + + if (!ret) + return false; + + if (hash) + return utils.isEqual(s[1], hash); + + return true; }; script.isNullData = function isNullData(s) { diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index ff56709d..ea1909fa 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -3,6 +3,7 @@ var bn = require('bn.js'); var bcoin = require('../bcoin'); var utils = bcoin.utils; var assert = utils.assert; +var constants = bcoin.protocol.constants; function TX(data, block) { if (!(this instanceof TX)) @@ -111,6 +112,110 @@ TX.prototype._inputIndex = function _inputIndex(hash, index) { return -1; }; +TX.prototype.signature = function(input, key) { + // Get the previous output's subscript + var s = input.out.tx.getSubscript(input.out.index); + + // Get the hash of the current tx, minus the other inputs, plus the sighash. + var hash = this.subscriptHash(tx.inputs.indexOf(input), s, type); + + // Sign the transaction with our one input + var signature = bcoin.ecdsa.sign(hash, key).toDER(); + + // Add the sighash as a single byte to the signature + signature = signature.concat(constants.hashType[type]); + + return signature; +}; + +// Build the scriptSigs for inputs, excluding the signatures +TX.prototype.scriptInput = function(input, pub, nsigs) { + // Get the previous output's subscript + var s = input.out.tx.getSubscript(input.out.index); + + // P2PKH and simple tx + if (bcoin.script.isPubkeyhash(s) || bcoin.script.isSimplePubkeyhash(s)) { + input.script = [ constants.opcodes['0'], pub ]; + return; + } + + // Multisig + // raw format: OP_FALSE [sig-1] [sig-2] ... + if (bcoin.script.isMultisig(s)) { + if (!nsigs) { + throw new Error('`nsigs` is required for multisig'); + } + input.script = [ constants.opcodes['false'] ]; + for (var i = 0; i < nsigs; i++) { + input.script[i + 1] = constants.opcodes['0']; + } + return; + } + + // P2SH multisig + // p2sh format: OP_FALSE [sig-1] [sig-2] ... [redeem-script] + if (bcoin.script.isScripthash(s)) { + input.script = [ constants.opcodes['false'] ]; + var m = pub[0] - constants.opcodes['1'] + 1; + for (var i = 0; i < m; i++) { + input.script[i + 1] = constants.opcodes['0']; + } + // P2SH requires the redeem script after signatures + if (bcoin.script.isScripthash(s)) { + input.script.push(pub); + } + return; + } + + throw new Error('could not identify prev_out type'); +}; + +// Sign the now-built scriptSigs +TX.prototype.signInput = function(input, key) { + // Get the previous output's subscript + var s = input.out.tx.getSubscript(input.out.index); + + // Get the hash of the current tx, minus the other inputs, plus the sighash. + var hash = this.subscriptHash(tx.inputs.indexOf(input), s, type); + + // Sign the transaction with our one input + var signature = bcoin.ecdsa.sign(hash, key).toDER(); + + // Add the sighash as a single byte to the signature + signature = signature.concat(constants.hashType[type]); + + // P2PKH and simple tx + if (bcoin.script.isPubkeyhash(s) || bcoin.script.isSimplePubkeyhash(s)) { + input.script[0] = signature; + return; + } + + // Multisig + // empty array == OP_FALSE == OP_0 + // raw format: OP_FALSE [sig-1] [sig-2] ... + // p2sh format: OP_FALSE [sig-1] [sig-2] ... [redeem-script] + if (bcoin.script.isMultisig(s) || bcoin.script.isScripthash(s)) { + var l = input.script.length; + if (bcoin.script.isScripthash(s)) { + l--; + } + for (var i = 0; i < l; i++) { + input.script[i + 1] = signature; + } + } +}; + +// Build the scriptSig and sign it +TX.prototype.scriptSig = function(input, key, pub, nsigs) { + // Build script for input + tx.scriptInput(input, pub, nsigs); + + // Sign input + tx.signInput(input, key); + + return this.input.script; +}; + TX.prototype.input = function input(i, index) { this._input(i, index); return this; @@ -155,7 +260,7 @@ TX.prototype.out = function out(output, value) { // outputs: [ [ 2 ], 'key1', 'key2', [ 2 ], 'checkmultisig' ] // in reality: // outputs: [ [ 2 ], [0,1,...], [2,3,...], [ 2 ], 'checkmultisig' ] - } else if (bcoin.wallet.validAddress(output.address, 'p2sh')) { + } else if (bcoin.wallet.validateAddress(output.address, 'p2sh')) { // p2sh transaction // https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki // hash160 [20-byte-redeemscript-hash] equal @@ -224,6 +329,9 @@ TX.prototype.verify = function verify(index, force) { var subscript = input.out.tx.getSubscript(input.out.index); var hash = this.subscriptHash(i, subscript, 'all'); + // XXX Deal with different hashTypes besides `all` + // var hash = this.subscriptHash.bind(this, i, subscript); + var stack = []; bcoin.script.execute(input.script, stack); var prev = input.out.tx.outputs[input.out.index].script; diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 0fd52154..78309e2f 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -29,7 +29,7 @@ function Wallet(options, passphrase) { this.key = null; this.loaded = false; this.lastTs = 0; - this.publicKeys = options.publicKeys; + this.sharedKeys = options.sharedKeys; if (options.priv instanceof bcoin.hd.priv) { this.hd = options.priv; @@ -58,24 +58,28 @@ function Wallet(options, passphrase) { this.fee = 10000; this.dust = 5460; + this.addressType = options.addressType || 'normal'; + + // Multisig + this.sharedKeys = (options.sharedKeys || []).map(utils.toKeyArray); this.m = options.m || 1; this.n = options.n || 1; - this.publicKeys = options.publicKeys; - if (this.publicKeys) { - if (this.publicKeys.length < this.n) { - this.publicKeys.push(this.getPublicKey()); - } + // Use p2sh multisig by default + if (!options.addressType && this.sharedKeys.length) { + this.addressType = 'p2sh'; + } - if (this.m < 1 || this.m > this.n) { - throw new Error('m ranges between 1 and n'); - } - if (this.n < 1 || this.n > 7) { - throw new Error('n ranges between 1 and 7'); - } - if (this.publicKeys.length !== this.n) { - throw new Error(this.n + ' public keys required'); - } + if (this.m < 1 || this.m > this.n) { + throw new Error('m ranges between 1 and n'); + } + + if (this.n < 1 || this.n > 7) { + throw new Error('n ranges between 1 and 7'); + } + + if (this.sharedKeys.length !== this.m - 1) { + throw new Error(this.m + ' public keys required'); } this._init(); @@ -142,7 +146,12 @@ Wallet.prototype.getPrivateKey = function getPrivateKey(enc) { }; Wallet.prototype.getPublicKey = function getPublicKey(enc) { - var pub = this.key.getPublic(this.compressed, 'array'); + var pub; + if (this.addressType === 'p2sh') + pub = this.getRedemption(); + else + pub = this.key.getPublic(this.compressed, 'array'); + if (enc === 'base58') return utils.toBase58(pub); else if (enc === 'hex') @@ -162,7 +171,7 @@ Wallet.prototype.getAddress = function getAddress() { Wallet.hash2addr = function hash2addr(hash, version) { hash = utils.toArray(hash, 'hex'); - version = constants.addr[version || 'normal']; + version = constants.addr[version || this.addressType]; hash = [ version ].concat(hash); var addr = hash.concat(utils.checksum(hash)); @@ -173,7 +182,7 @@ Wallet.addr2hash = function addr2hash(addr, version) { if (!Array.isArray(addr)) addr = utils.fromBase58(addr); - version = constants.addr[version || 'normal']; + version = constants.addr[version || this.addressType]; if (addr.length !== 25) return []; @@ -187,15 +196,8 @@ Wallet.addr2hash = function addr2hash(addr, version) { return addr.slice(1, -4); }; -Wallet.validAddress = function validAddr(addr, version) { - if (!addr) - return false; - - return !!Wallet.addr2hash(addr, version).length; -}; - -Wallet.prototype.validateAddress = function validateAddress(addr) { - var p = Wallet.addr2hash(addr); +Wallet.prototype.validateAddress = function validateAddress(addr, version) { + var p = Wallet.addr2hash(addr, version); return p.length !== 0; }; Wallet.validateAddress = Wallet.prototype.validateAddress; @@ -219,10 +221,8 @@ Wallet.prototype.ownOutput = function ownOutput(tx, index) { if (bcoin.script.isMultisig(s, key)) return true; - if (bcoin.script.isScripthash(s) - && utils.isEqual(s[1], this.getP2SHHash())) { + if (bcoin.script.isScripthash(s, hash)) return true; - } return false; }, this); @@ -254,10 +254,8 @@ Wallet.prototype.ownInput = function ownInput(tx, index) { if (bcoin.script.isMultisig(s, key)) return true; - if (bcoin.script.isScripthash(s) - && utils.isEqual(s[1], this.getP2SHHash())) { + if (bcoin.script.isScripthash(s, hash)) return true; - } return false; }, this); @@ -308,8 +306,6 @@ Wallet.prototype.sign = function sign(tx, type, inputs, off) { // raw format: OP_FALSE [sig-1] [sig-2] ... // p2sh format: OP_FALSE [sig-1] [sig-2] ... [redeem-script] if (bcoin.script.isMultisig(s) || bcoin.script.isScripthash(s)) { - // XXX Check own? - // || (bcoin.script.isScripthash(s) && utils.isEqual(s[1], this.getP2SHHash())) { if (!input.script || !input.script.length) { input.script = [ [], signature ]; } else if (!~input.script.indexOf(signature)) { @@ -320,7 +316,7 @@ Wallet.prototype.sign = function sign(tx, type, inputs, off) { // P2SH requires a redeem script after signatures if (bcoin.script.isScripthash(s)) { if (input.script.length - 1 === this.m) { - input.script.push(this.getP2SHRedemption()); + input.script.push(this.getRedemption()); } } @@ -330,6 +326,30 @@ Wallet.prototype.sign = function sign(tx, type, inputs, off) { return inputs.length; }; +Wallet.prototype.sign = function sign(tx, type, inputs) { + if (!type) + type = 'all'; + + var pub = this.getPublicKey(); + var key = this.key; + var nsigs = this.m; + + inputs = inputs || tx.inputs; + + // Add signature script to each input + inputs = inputs.filter(function(input) { + // Filter inputs that this wallet own + if (!input.out.tx || !this.ownOutput(input.out.tx)) + return false; + + tx.scriptSig(input, key, pub, nsigs); + + return true; + }, this); + + return inputs.length; +}; + Wallet.prototype.addTX = function addTX(tx, block) { return this.tx.add(tx); }; @@ -445,56 +465,29 @@ Wallet.prototype.getChange = function fill(tx) { }; /** - * P2SH (and Multisig) + * P2SH+Multisig Redemption */ -Wallet.prototype.getP2SHHash = function() { - return this.getP2SH().hash; -}; - -Wallet.prototype.getP2SHAddress = function() { - return this.getP2SH().address; -}; - -Wallet.prototype.getP2SHRedemption = function() { - return this.getP2SH().redemption; -}; - -Wallet.prototype.getP2SH = function(redeem) { - this.publicKeys = this.publicKeys.map(function(key) { - return utils.toKeyArray(key); - }); - var redemption = redeem || this._createMultisigRedemption(); - var hash = utils.ripasha(redemption); - return { - hash: hash, - address: Wallet.hash2addr(hash, 'p2sh'), - redemption: redemption - }; -}; - -Wallet.prototype._createMultisigRedemption = function() { - var publicKeys = this.publicKeys; - var mcode = constants.opcodes['1'] + (this.m - 1); - var ncode = constants.opcodes['1'] + (this.n - 1); - var redemption = []; - redemption.push(mcode); - this.publicKeys.forEach(function(pubkey) { - redemption.push(pubkey.length); - redemption = redemption.concat(pubkey); - }, this); - redemption.push(ncode); - redemption.push(constants.opcodes.checkmultisig); - return redemption; +Wallet.prototype.getRedemption = function() { + var sharedKeys = this.sharedKeys.slice().map(utils.toKeyArray); + if (sharedKeys.length < this.m) { + var pub = this.key.getPublic(this.compressed, 'array'); + sharedKeys.push(pub); + } + return bcoin.script.redemption(sharedKeys, m, n); }; Wallet.prototype.toJSON = function toJSON() { return { v: 1, type: 'wallet', - pub: this.getPublicKey('base58'), + pub: utils.toBase58(this.key.getPublic(this.compressed, 'array')), priv: this.getPrivateKey('base58'), - tx: this.tx.toJSON() + tx: this.tx.toJSON(), + addressType: this.addressType, + sharedKeys: utils.toBase58(this.sharedKeys), + m: this.m, + n: this.n }; }; @@ -528,7 +521,11 @@ Wallet.fromJSON = function fromJSON(json) { var w = new Wallet({ priv: priv, pub: pub, - compressed: compressed + compressed: compressed, + addressType: json.addressType, + sharedKeys: json.sharedKeys, + m: json.m, + n: json.n }); w.tx.fromJSON(json.tx); From a2f13d94c3566952710e96f14c8a606833ea68b8 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 5 Dec 2015 03:36:49 -0800 Subject: [PATCH 13/59] fix address problems. update elliptic to 3.0.3. --- lib/bcoin.js | 6 +----- lib/bcoin/hd.js | 13 +++---------- lib/bcoin/tx.js | 6 +++--- lib/bcoin/wallet.js | 25 ++++++++++++++----------- package.json | 2 +- 5 files changed, 22 insertions(+), 30 deletions(-) diff --git a/lib/bcoin.js b/lib/bcoin.js index 7b64c3c2..a638a1af 100644 --- a/lib/bcoin.js +++ b/lib/bcoin.js @@ -1,11 +1,7 @@ var bcoin = exports; var elliptic = require('elliptic'); -if (elliptic.ec) { - bcoin.ecdsa = elliptic.ec('secp256k1'); -} else { - bcoin.ecdsa = elliptic.ecdsa(elliptic.nist.secp256k1); -} +bcoin.ecdsa = elliptic.ec('secp256k1'); bcoin.utils = require('./bcoin/utils'); bcoin.bloom = require('./bcoin/bloom'); bcoin.protocol = require('./bcoin/protocol'); diff --git a/lib/bcoin/hd.js b/lib/bcoin/hd.js index 8a77237b..6e67693f 100644 --- a/lib/bcoin/hd.js +++ b/lib/bcoin/hd.js @@ -62,16 +62,7 @@ var crypto = require('crypto'); var english = require('../../etc/english.json'); -var ec; -if (!elliptic.curves) { - ec = elliptic.nist.secp256k1; - ec.curve.g = ec.g; - ec.curve.n = ec.n; -} else { - ec = elliptic.curves.secp256k1; -} -var ecPoint = ec.curve.point.bind(ec.curve); -var ecPointFromX = ec.curve.pointFromX.bind(ec.curve); +var ec = elliptic.curves.secp256k1; /** * HD Seeds @@ -333,6 +324,8 @@ HDPriv.prototype.derive = function(index, hard) { var leftPart = new bn(hash.slice(0, 32)); var chainCode = hash.slice(32, 64); + // XXX This causes a call stack overflow with bn.js@4.0.5 and elliptic@3.0.3 + // Example: new bn(0).mod(ec.curve.n) var privateKey = leftPart.add(new bn(this.privateKey)).mod(ec.curve.n).toArray(); return new HDPriv({ diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index ea1909fa..b1243ba5 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -242,7 +242,7 @@ TX.prototype.out = function out(output, value) { var keys = output.keys || output.address; if (keys === output.address) { keys = keys.map(function(address) { - return bcoin.wallet.addr2hash(address); + return bcoin.wallet.addr2hash(address, 'normal'); }); } keys = keys.map(function(key) { @@ -275,7 +275,7 @@ TX.prototype.out = function out(output, value) { script = [ 'dup', 'hash160', - bcoin.wallet.addr2hash(output.address), + bcoin.wallet.addr2hash(output.address, 'normal'), 'eqverify', 'checksig' ]; @@ -383,7 +383,7 @@ TX.prototype.inputAddrs = function inputAddrs() { }).map(function(input) { var pub = input.script[1]; var hash = utils.ripesha(pub); - return bcoin.wallet.hash2addr(hash); + return bcoin.wallet.hash2addr(hash, 'normal'); }); }; diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 78309e2f..6f1eb0f6 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -46,18 +46,14 @@ function Wallet(options, passphrase) { entropy: hash.sha256().update(options.passphrase).digest() }); } else if (options.priv || options.pub) { - this.key = bcoin.ecdsa.keyPair(options.priv || options.pub, 'hex'); + this.key = bcoin.ecdsa.keyPair({ + priv: options.priv, + pub: options.pub + }); } else { this.key = bcoin.ecdsa.genKeyPair(); } - this.prefix = 'bt/' + this.getAddress() + '/'; - this.tx = new bcoin.txPool(this); - - // Just a constants, actually - this.fee = 10000; - this.dust = 5460; - this.addressType = options.addressType || 'normal'; // Multisig @@ -82,6 +78,13 @@ function Wallet(options, passphrase) { throw new Error(this.m + ' public keys required'); } + this.prefix = 'bt/' + this.getAddress() + '/'; + this.tx = new bcoin.txPool(this); + + // Just a constants, actually + this.fee = 10000; + this.dust = 5460; + this._init(); } inherits(Wallet, EventEmitter); @@ -165,13 +168,13 @@ Wallet.prototype.getHash = function getHash() { }; Wallet.prototype.getAddress = function getAddress() { - return Wallet.hash2addr(this.getHash()); + return Wallet.hash2addr(this.getHash(), this.addressType); }; Wallet.hash2addr = function hash2addr(hash, version) { hash = utils.toArray(hash, 'hex'); - version = constants.addr[version || this.addressType]; + version = constants.addr[version || 'normal']; hash = [ version ].concat(hash); var addr = hash.concat(utils.checksum(hash)); @@ -182,7 +185,7 @@ Wallet.addr2hash = function addr2hash(addr, version) { if (!Array.isArray(addr)) addr = utils.fromBase58(addr); - version = constants.addr[version || this.addressType]; + version = constants.addr[version || 'normal']; if (addr.length !== 25) return []; diff --git a/package.json b/package.json index 26759a4d..9037ca85 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "dependencies": { "async": "^0.8.0", "bn.js": "^0.10.0", - "elliptic": "^0.14.1", + "elliptic": "^3.0.3", "hash.js": "^0.2.0", "inherits": "^2.0.1" }, From 40fca0dda862deca608bb9326b1e7fa288418274 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 5 Dec 2015 03:55:08 -0800 Subject: [PATCH 14/59] remove noSign and getChange for now. --- lib/bcoin/wallet.js | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 6f1eb0f6..8d623e22 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -373,17 +373,9 @@ Wallet.prototype.balance = function balance() { return this.tx.balance(); }; -Wallet.prototype.fill = function fill(tx, options, cb) { - if ((cb && typeof cb === 'object') || options == null) { - cb = options; - options = {}; - } +Wallet.prototype.fill = function fill(tx, cb) { cb = utils.asyncify(cb); - if (options._getChange) { - tx = tx.clone(); - } - // NOTE: tx should be prefilled with all outputs var cost = tx.funds('out'); @@ -434,10 +426,6 @@ Wallet.prototype.fill = function fill(tx, options, cb) { // How much money is left after sending outputs var left = tx.funds('in').sub(total); - if (options._getChange) { - return left; - } - // Not enough money, transfer everything to owner if (left.cmpn(this.dust) < 0) { // NOTE: that this output is either `postCost` or one of the `dust` values @@ -452,21 +440,14 @@ Wallet.prototype.fill = function fill(tx, options, cb) { tx.outputs[tx.outputs.length - 1].value = left; // Sign transaction - if (options.sign === false) { - this.signEmpty(tx); - } else { - this.sign(tx); - } + // XXX Do not necessarily call this here + this.sign(tx); cb(null, tx); return tx; }; -Wallet.prototype.getChange = function fill(tx) { - return this.fill(tx, { _getChange: true }); -}; - /** * P2SH+Multisig Redemption */ From 916fca9c2f8b556c5df501bbade251adb38106dd Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 5 Dec 2015 04:16:41 -0800 Subject: [PATCH 15/59] update hash.js. use for sha512-hmacs. --- lib/bcoin/hd.js | 31 ++++++++++++++++++++++++------- lib/bcoin/wallet.js | 5 +---- package.json | 2 +- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/lib/bcoin/hd.js b/lib/bcoin/hd.js index 6e67693f..fcc9f585 100644 --- a/lib/bcoin/hd.js +++ b/lib/bcoin/hd.js @@ -58,7 +58,6 @@ var utils = bcoin.utils; var assert = utils.assert; var EventEmitter = require('events').EventEmitter; -var crypto = require('crypto'); var english = require('../../etc/english.json'); @@ -91,8 +90,7 @@ HDSeed.prototype.createSeed = function(passphrase) { }; HDSeed._entropy = function(size) { - // XXX Do not use crypto - return Array.prototype.slice.call(crypto.randomBytes(size)); + return randomBytes(size); }; HDSeed._mnemonic = function(entropy) { @@ -361,7 +359,7 @@ HDPriv._getIndexes = function(path) { if (!step || step[0] === '-') { return null; } - var index = +step; // cast to number + var index = +step; if (hard) { index += HARDENED; } @@ -563,11 +561,30 @@ HDPub.prototype.deriveString = function(path) { * Helpers */ +var isBrowser = (typeof process !== 'undefined' && process.browser) + || typeof window !== 'undefined'; + function sha512hmac(data, salt) { - // XXX Do not use crypto + if (isBrowser) { + var hmac = hash.hmac(hash.sha512, utils.toArray(salt)); + return hmac.update(utils.toArray(data)).digest(); + } + var crypto = require('crypto'); var hmac = crypto.createHmac('sha512', new Buffer(salt)); - var hash = hmac.update(new Buffer(data)).digest(); - return Array.prototype.slice.call(hash); + var h = hmac.update(new Buffer(data)).digest(); + return Array.prototype.slice.call(h); +} + +function randomBytes(size) { + if (isBrowser) { + var a = Uint8Array(size); + (window.crypto || window.msCrypto).getRandomValues(a); + var buf = new Array(size); + utils.copy(a, buf, 0); + return buf; + } + var crypto = require('crypto'); + return Array.prototype.slice.call(crypto.randomBytes(size)); } /** diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 8d623e22..271a3edb 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -448,10 +448,7 @@ Wallet.prototype.fill = function fill(tx, cb) { return tx; }; -/** - * P2SH+Multisig Redemption - */ - +// P2SH Multisig redeem script Wallet.prototype.getRedemption = function() { var sharedKeys = this.sharedKeys.slice().map(utils.toKeyArray); if (sharedKeys.length < this.m) { diff --git a/package.json b/package.json index 9037ca85..bfa23f89 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "async": "^0.8.0", "bn.js": "^0.10.0", "elliptic": "^3.0.3", - "hash.js": "^0.2.0", + "hash.js": "^1.0.3", "inherits": "^2.0.1" }, "devDependencies": { From 46a5ca7500e9ef0a6300627bea54ec66d25497d1 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 5 Dec 2015 04:18:21 -0800 Subject: [PATCH 16/59] minor fix for wallet.fill. --- lib/bcoin/wallet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 271a3edb..4936126d 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -399,7 +399,7 @@ Wallet.prototype.fill = function fill(tx, cb) { unspent.every(addInput, this); // Add dummy output (for `left`) to calculate maximum TX size - tx.out(options.change || this, new bn(0)); + tx.out(this, new bn(0)); // Change fee value if it is more than 1024 bytes // (10000 satoshi for every 1024 bytes) From c96a12bf2eb7a4eb3c6827c70f1dd2c52f85dbd5 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 5 Dec 2015 04:59:18 -0800 Subject: [PATCH 17/59] fix validateAddress. cleanup. --- lib/bcoin/chain.js | 56 +-------------------------------------------- lib/bcoin/peer.js | 7 ++++-- lib/bcoin/wallet.js | 2 ++ 3 files changed, 8 insertions(+), 57 deletions(-) diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index ad38b748..df87cfe2 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -461,42 +461,6 @@ Chain.prototype.locatorHashes = function(index) { return hashes; }; -Chain.prototype.locatorHashes = function(index) { - var chain = this.index.hashes; - var hashes = []; - var top = chain.length - 1; - var step = 1, start = 0; - - if (typeof index === 'string') { - for (i = top; i >= 0; i--) { - if (chain[i] === index) { - top = i; - break; - } - } - } else if (typeof index === 'number') { - top = index; - } - - for (var i = top; i > 0; i -= step, ++start) { - if (start >= 10) step *= 2; - hashes.push(chain[i]); - } - - hashes.push(chain[0]); - - return hashes; -}; - -Chain.prototype.__defineGetter__('orphan_bmap', function() { - var self = this; - return Object.keys(this.orphan.map).reduce(function(out, prev) { - var orphan = self.orphan.map[prev]; - out[orphan.hash('hex')] = orphan; - return out; - }, {}); -}); - Chain.prototype.getOrphanRoot = function getOrphanRoot(hash) { var self = this; @@ -504,31 +468,13 @@ Chain.prototype.getOrphanRoot = function getOrphanRoot(hash) { hash = hash.hash('hex'); } - var orphans = this.orphan_bmap; + var orphans = this.orphan.bmap; - /* - var orphanRoot = hash; - var last = orphanRoot; - while (orphans[last]) { - orphanRoot = last; - last = orphans[last].prevBlock; - } - */ - - // accurate: var orphanRoot = hash; while (orphans[orphanRoot.prevBlock]) { orphanRoot = orphans[orphanRoot.prevBlock]; } - /* - if hash stop gets last desired block, it should be: - var orphanRoot = hash; - while (orphans[orphanRoot]) { - orphanRoot = orphans[orphanRoot].prevBlock; - } - */ - return orphanRoot; }; diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index 8bb9d431..215ca533 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -92,7 +92,10 @@ Peer.prototype._init = function init() { if (this.pool.options.fullNode) { this.once('version', function() { var ip = self.socket && self.socket.remoteAddress || '0.0.0.0'; - self.pool.emit('debug', 'version (%s): loading locator hashes for getblocks', ip); + self.pool.emit('debug', + 'Sent version (%s): height=%s', + ip, this.pool.chain.getStartHeight()); + self.pool.emit('debug', 'version (%s): sending locator hashes', ip); self.chain.on('load', function() { self.loadHeaders(self.chain.locatorHashes(), 0); }); @@ -460,7 +463,7 @@ Peer.prototype._handleHeaders = function handleHeaders(headers) { for (var i = 0; i < headers.length; i++) { var header = headers[i]; var hash = header.hash; - // if (this.chain.orphan_bmap[hash]) { + // if (this.chain.orphan.bmap[hash]) { if (this.chain.orphan.map[header.prevBlock]) { this.loadHeaders(this.chain.locatorHashes(), this.chain.getOrphanRoot(hash)); continue; diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 4936126d..df4579d1 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -200,6 +200,8 @@ Wallet.addr2hash = function addr2hash(addr, version) { }; Wallet.prototype.validateAddress = function validateAddress(addr, version) { + if (!addr) + return false; var p = Wallet.addr2hash(addr, version); return p.length !== 0; }; From 1a2fefa3d9af208a38b3624f53838015f673320f Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 5 Dec 2015 14:43:00 -0800 Subject: [PATCH 18/59] update bn.js --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bfa23f89..96b9811f 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "homepage": "https://github.com/indutny/bcoin", "dependencies": { "async": "^0.8.0", - "bn.js": "^0.10.0", + "bn.js": "^4.5.0", "elliptic": "^3.0.3", "hash.js": "^1.0.3", "inherits": "^2.0.1" From 3f2d53b414ec310a7a03b3273f0880382b497f9a Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 5 Dec 2015 18:19:52 -0800 Subject: [PATCH 19/59] hd.js: workaround for latest bn.js. --- lib/bcoin/hd.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/bcoin/hd.js b/lib/bcoin/hd.js index fcc9f585..bf8975cc 100644 --- a/lib/bcoin/hd.js +++ b/lib/bcoin/hd.js @@ -324,7 +324,11 @@ HDPriv.prototype.derive = function(index, hard) { // XXX This causes a call stack overflow with bn.js@4.0.5 and elliptic@3.0.3 // Example: new bn(0).mod(ec.curve.n) - var privateKey = leftPart.add(new bn(this.privateKey)).mod(ec.curve.n).toArray(); + //var privateKey = leftPart.add(new bn(this.privateKey)).mod(ec.curve.n).toArray(); + + // Use this as a workaround: + var n = new bn('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', 'hex'); + var privateKey = leftPart.add(new bn(this.privateKey)).mod(n).toArray(); return new HDPriv({ // version: this.version, From db0da78ae57bbc18ac75b47fff63400924b0ed23 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 5 Dec 2015 18:34:18 -0800 Subject: [PATCH 20/59] only start calculating startHeight and locatorHashes after chain load. --- lib/bcoin/chain.js | 8 ++------ lib/bcoin/peer.js | 4 +--- lib/bcoin/pool.js | 20 ++++++++++++++------ 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index df87cfe2..7a901a88 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -413,18 +413,14 @@ Chain.prototype.getLast = function getLast(cb) { return cb(this.index.hashes[this.index.hashes.length - 1]); }; -Chain.prototype.getStartHeight = function getLast(cb) { +Chain.prototype.getStartHeight = function getStartHeight() { if (!this.options.fullNode) { if (this.options.startHeight != null) { return this.options.startHeight; } return 0; } - for (var i = 0; i < this.index.heights.length; i++) { - if (this.index.heights[i + 1] !== this.index.heights[i] + 1) { - return this.index.heights[i]; - } - } + return this.index.heights[this.index.heights.length - 1]; }; Chain.prototype.locatorHashes = function(index) { diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index 215ca533..8b8212c8 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -96,9 +96,7 @@ Peer.prototype._init = function init() { 'Sent version (%s): height=%s', ip, this.pool.chain.getStartHeight()); self.pool.emit('debug', 'version (%s): sending locator hashes', ip); - self.chain.on('load', function() { - self.loadHeaders(self.chain.locatorHashes(), 0); - }); + self.loadHeaders(self.chain.locatorHashes(), 0); }); } diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index b56b89ba..07e127e1 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -7,6 +7,8 @@ var utils = bcoin.utils; var assert = utils.assert; function Pool(options) { + var self = this; + if (!(this instanceof Pool)) return new Pool(options); @@ -90,7 +92,18 @@ function Pool(options) { this.createConnection = options.createConnection; assert(this.createConnection); - this._init(); + this.chain.on('debug', function() { + var args = Array.prototype.slice.call(arguments); + self.emit.apply(self, ['debug'].concat(args)); + }); + + if (!this.chain.loading) { + this._init(); + } else { + this.chain.once('load', function() { + self._init(); + }); + } } inherits(Pool, EventEmitter); module.exports = Pool; @@ -109,11 +122,6 @@ Pool.prototype._init = function _init() { self._scheduleRequests(); self._loadRange(preload); }); - - this.chain.on('debug', function() { - var args = Array.prototype.slice.call(arguments); - self.emit.apply(self, ['debug'].concat(args)); - }); }; Pool.prototype._addLoader = function _addLoader() { From 4f76b9f18c71afaf1de95c8f2f810aa6371ac4a4 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 5 Dec 2015 18:58:27 -0800 Subject: [PATCH 21/59] fix scriptInput/signInput for multisig/p2sh. --- lib/bcoin/tx.js | 51 ++++++++++++++++++++++++++------------------- lib/bcoin/wallet.js | 2 +- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index b1243ba5..40445e3e 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -112,7 +112,7 @@ TX.prototype._inputIndex = function _inputIndex(hash, index) { return -1; }; -TX.prototype.signature = function(input, key) { +TX.prototype.signature = function(input, key, type) { // Get the previous output's subscript var s = input.out.tx.getSubscript(input.out.index); @@ -142,13 +142,11 @@ TX.prototype.scriptInput = function(input, pub, nsigs) { // Multisig // raw format: OP_FALSE [sig-1] [sig-2] ... if (bcoin.script.isMultisig(s)) { - if (!nsigs) { + if (!nsigs) throw new Error('`nsigs` is required for multisig'); - } input.script = [ constants.opcodes['false'] ]; - for (var i = 0; i < nsigs; i++) { + for (var i = 0; i < nsigs; i++) input.script[i + 1] = constants.opcodes['0']; - } return; } @@ -157,21 +155,22 @@ TX.prototype.scriptInput = function(input, pub, nsigs) { if (bcoin.script.isScripthash(s)) { input.script = [ constants.opcodes['false'] ]; var m = pub[0] - constants.opcodes['1'] + 1; - for (var i = 0; i < m; i++) { + for (var i = 0; i < m; i++) input.script[i + 1] = constants.opcodes['0']; - } // P2SH requires the redeem script after signatures - if (bcoin.script.isScripthash(s)) { + if (bcoin.script.isScripthash(s)) input.script.push(pub); - } return; } - throw new Error('could not identify prev_out type'); + throw new Error('scriptInput(): could not identify prev_out type'); }; // Sign the now-built scriptSigs -TX.prototype.signInput = function(input, key) { +TX.prototype.signInput = function(input, key, type) { + if (!type) + type = 'all'; + // Get the previous output's subscript var s = input.out.tx.getSubscript(input.out.index); @@ -191,29 +190,39 @@ TX.prototype.signInput = function(input, key) { } // Multisig - // empty array == OP_FALSE == OP_0 // raw format: OP_FALSE [sig-1] [sig-2] ... // p2sh format: OP_FALSE [sig-1] [sig-2] ... [redeem-script] if (bcoin.script.isMultisig(s) || bcoin.script.isScripthash(s)) { - var l = input.script.length; - if (bcoin.script.isScripthash(s)) { - l--; - } - for (var i = 0; i < l; i++) { - input.script[i + 1] = signature; + var len = input.script.length; + + if (bcoin.script.isScripthash(s)) + len--; + + for (var i = 1; i < len; i++) { + // Already signed + if (utils.isEqual(input.script[i], signature)) + break; + + if (input.script[i] === constants.opcodes['0']) { + input.script[i] = signature; + break; + } } + return; } + + throw new Error('signInput(): could not identify prev_out type'); }; // Build the scriptSig and sign it -TX.prototype.scriptSig = function(input, key, pub, nsigs) { +TX.prototype.scriptSig = function(input, key, pub, type, nsigs) { // Build script for input tx.scriptInput(input, pub, nsigs); // Sign input - tx.signInput(input, key); + tx.signInput(input, key, type); - return this.input.script; + return input.script; }; TX.prototype.input = function input(i, index) { diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index df4579d1..db20d8d9 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -347,7 +347,7 @@ Wallet.prototype.sign = function sign(tx, type, inputs) { if (!input.out.tx || !this.ownOutput(input.out.tx)) return false; - tx.scriptSig(input, key, pub, nsigs); + tx.scriptSig(input, key, pub, type, nsigs); return true; }, this); From 6295642f208d322c93116b5e7354559300d5d161 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 5 Dec 2015 20:23:33 -0800 Subject: [PATCH 22/59] remove useless if statement. --- lib/bcoin/tx.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 40445e3e..c49cf046 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -158,8 +158,7 @@ TX.prototype.scriptInput = function(input, pub, nsigs) { for (var i = 0; i < m; i++) input.script[i + 1] = constants.opcodes['0']; // P2SH requires the redeem script after signatures - if (bcoin.script.isScripthash(s)) - input.script.push(pub); + input.script.push(pub); return; } From ae394fad114901f05b54c6f80e851c6b9fadb192 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 5 Dec 2015 20:53:06 -0800 Subject: [PATCH 23/59] improve tx.maxSize for p2sh and multisig. --- lib/bcoin/tx.js | 46 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index c49cf046..881eac3b 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -355,7 +355,10 @@ TX.prototype.isCoinbase = function isCoinbase() { return this.inputs.length === 1 && +this.inputs[0].out.hash === 0; }; -TX.prototype.maxSize = function maxSize() { +TX.prototype.maxSize = function maxSize(m, n) { + m = m || 1; + n = n || 1; + // Create copy with 0-script inputs var copy = this.clone(); copy.inputs.forEach(function(input) { @@ -367,7 +370,8 @@ TX.prototype.maxSize = function maxSize() { // Add size for signatures and public keys copy.inputs.forEach(function(input) { var s = input.out.tx.outputs[input.out.index].script; - if (bcoin.script.isPubkeyhash(s)) { + + if (bcoin.script.isPubkeyhash(s) || bcoin.script.isSimplePubkeyhash(s)) { // Signature + len size += 74; // Pub key + len @@ -375,11 +379,39 @@ TX.prototype.maxSize = function maxSize() { return; } - // Multisig - // Empty byte - size += 1; - // Signature + len - size += 74; + if (bcoin.script.isMultisig(s)) { + // Multisig + // Empty byte + size += 1; + // Signature + len + size += 74 * m; + return; + } + + // 1 empty byte + // 1 mcode byte + // loop: + // 1 byte key length + // 65? byte key + // 1 ncode byte + // 1 checkmultisig byte + if (bcoin.script.isScripthash(s)) { + // Multisig + // Empty byte + size += 1; + // Signature + len + size += 74 * m; + // Redeem script + // m byte + size += 1; + // 1 byte length + 65 byte pubkey + size += 66 * n; + // n byte + size += 1; + // checkmultisig byte + size += 1; + return; + } }); return size; From d7bc48fc2ede916f179ac05dfe785bc89506fe3e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 5 Dec 2015 21:36:02 -0800 Subject: [PATCH 24/59] move wallet.fill to tx object. --- lib/bcoin/tx.js | 199 +++++++++++++++++++++++++++++++++++--------- lib/bcoin/wallet.js | 8 ++ 2 files changed, 169 insertions(+), 38 deletions(-) diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 881eac3b..a1523c80 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -41,6 +41,12 @@ function TX(data, block) { // ps = Pending Since this.ps = this.ts === 0 ? +new Date() / 1000 : 0; + + this.m = data.m || 1; + this.n = data.n || 1; + this.change = data.change || null; + this.fee = data.fee || 10000; + this.dust = 5460; } module.exports = TX; @@ -57,6 +63,11 @@ TX.prototype.render = function render() { return bcoin.protocol.framer.tx(this); }; +TX.prototype.input = function input(i, index) { + this._input(i, index); + return this; +}; + TX.prototype._input = function _input(i, index) { if (i instanceof TX) i = { tx: i, index: index }; @@ -88,10 +99,10 @@ TX.prototype._input = function _input(i, index) { var index = this._inputIndex(hash, index); if (index !== -1) { var ex = this.inputs[index]; - - ex.out.tx = input.out.tx || ex.out.tx; - ex.seq = input.seq || ex.seq; - ex.script = input.script.length ? input.script : ex.script; + input.out.tx = input.out.tx || ex.out.tx; + input.seq = input.seq || ex.seq; + input.script = input.script.length ? input.script : ex.script; + this.inputs[index] = input; } else { this.inputs.push(input); index = this.inputs.length - 1; @@ -224,14 +235,10 @@ TX.prototype.scriptSig = function(input, key, pub, type, nsigs) { return input.script; }; -TX.prototype.input = function input(i, index) { - this._input(i, index); - return this; -}; - -TX.prototype.out = function out(output, value) { +TX.prototype.output = function output(output, value) { if (output instanceof bcoin.wallet) output = output.getAddress(); + if (typeof output === 'string') { output = { address: output, @@ -239,16 +246,26 @@ TX.prototype.out = function out(output, value) { }; } - var script = output.script ? output.script.slice() : []; + this.outputs.push({ + value: new bn(output.value), + script: this.scriptOutput(output) + }); +}; - if (Array.isArray(output.keys || output.address)) { +// compat +TX.prototype.out = TX.prototype.output; + +TX.prototype.scriptOutput = function(options) { + var script = []; + + if (Array.isArray(options.keys || options.address)) { // Raw multisig transaction // https://github.com/bitcoin/bips/blob/master/bip-0010.mediawiki // https://github.com/bitcoin/bips/blob/master/bip-0011.mediawiki // https://github.com/bitcoin/bips/blob/master/bip-0019.mediawiki // [required-sigs] [pubkey-hash1] [pubkey-hash2] ... [number-of-keys] checkmultisig - var keys = output.keys || output.address; - if (keys === output.address) { + var keys = options.keys || options.address; + if (keys === options.address) { keys = keys.map(function(address) { return bcoin.wallet.addr2hash(address, 'normal'); }); @@ -260,7 +277,7 @@ TX.prototype.out = function out(output, value) { return key; }); script = [ - [ output.minSignatures || keys.length ] + [ options.minSignatures || keys.length ] ].concat( keys, [ [ keys.length ], 'checkmultisig' ] @@ -268,33 +285,28 @@ TX.prototype.out = function out(output, value) { // outputs: [ [ 2 ], 'key1', 'key2', [ 2 ], 'checkmultisig' ] // in reality: // outputs: [ [ 2 ], [0,1,...], [2,3,...], [ 2 ], 'checkmultisig' ] - } else if (bcoin.wallet.validateAddress(output.address, 'p2sh')) { + } else if (bcoin.wallet.validateAddress(options.address, 'p2sh')) { // p2sh transaction // https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki // hash160 [20-byte-redeemscript-hash] equal script = [ 'hash160', - bcoin.wallet.addr2hash(output.address, 'p2sh'), + bcoin.wallet.addr2hash(options.address, 'p2sh'), 'eq' ]; - } else if (output.address) { + } else if (options.address) { // p2pkh transaction // dup hash160 [pubkey-hash] equalverify checksig script = [ 'dup', 'hash160', - bcoin.wallet.addr2hash(output.address, 'normal'), + bcoin.wallet.addr2hash(options.address, 'normal'), 'eqverify', 'checksig' ]; } - this.outputs.push({ - value: new bn(output.value), - script: script - }); - - return this; + return script; }; TX.prototype.getSubscript = function getSubscript(index) { @@ -347,6 +359,7 @@ TX.prototype.verify = function verify(index, force) { if (!res) return false; + // XXX sighash_all here? return stack.length > 0 && utils.isEqual(stack.pop(), [ 1 ]); }, this); }; @@ -355,10 +368,7 @@ TX.prototype.isCoinbase = function isCoinbase() { return this.inputs.length === 1 && +this.inputs[0].out.hash === 0; }; -TX.prototype.maxSize = function maxSize(m, n) { - m = m || 1; - n = n || 1; - +TX.prototype.maxSize = function maxSize() { // Create copy with 0-script inputs var copy = this.clone(); copy.inputs.forEach(function(input) { @@ -369,7 +379,11 @@ TX.prototype.maxSize = function maxSize(m, n) { // Add size for signatures and public keys copy.inputs.forEach(function(input) { - var s = input.out.tx.outputs[input.out.index].script; + // Get the previous output's script + // var s = input.out.tx.outputs[input.out.index].script; + + // Get the previous output's subscript + var s = input.out.tx.getSubscript(input.out.index); if (bcoin.script.isPubkeyhash(s) || bcoin.script.isSimplePubkeyhash(s)) { // Signature + len @@ -384,18 +398,25 @@ TX.prototype.maxSize = function maxSize(m, n) { // Empty byte size += 1; // Signature + len + var m = s[0][0] || this.m; + assert(m >= 1 && m <= 7); size += 74 * m; return; } - // 1 empty byte - // 1 mcode byte - // loop: - // 1 byte key length - // 65? byte key - // 1 ncode byte - // 1 checkmultisig byte if (bcoin.script.isScripthash(s)) { + var script = this.inputs[i].script; + var redeem, m, n; + if (script) { + redeem = script[script.length - 1]; + m = redeem[0] - constants.opcodes['1'] + 1; + n = redeem[redeem.length - 2] - constants.opcodes['1'] + 1; + } else { + m = this.m; + n = this.n; + } + assert(m >= 1 && m <= n); + assert(n >= 1 && n <= 7); // Multisig // Empty byte size += 1; @@ -412,11 +433,113 @@ TX.prototype.maxSize = function maxSize(m, n) { size += 1; return; } - }); + }, this); return size; }; +// Building a TX: +// 1. Add outputs: +// - this.output({ address: ..., value: ... }); +// - this.output({ address: ..., value: ... }); +// 2. Add inputs with utxos and change output: +// - this.fillUnspent(unspentItems, [changeAddr]); +// 3. Fill input scripts (for each input): +// - this.scriptInput(input, pub, [nsigs]) +// - this.signInput(input, key, [sigHashType]) +TX.prototype.utxos = function utxos(unspent) { + // NOTE: tx should be prefilled with all outputs + var cost = this.funds('out'); + + // Use initial fee for starters + var fee = 1; + + // total = cost + fee + var total = cost.add(new bn(this.fee)); + + var inputs = this.inputs.slice(); + var utxos = []; + + var lastAdded = 0; + function addInput(unspent, i) { + // Add new inputs until TX will have enough funds to cover both + // minimum post cost and fee + var index = this._input(unspent); + utxos.push(this.inputs[index]); + lastAdded++; + return this.funds('in').cmp(total) < 0; + } + + // Transfer `total` funds maximum + // var unspent = wallet.unspent(); + unspent.every(addInput, this); + + // Add dummy output (for `left`) to calculate maximum TX size + this.output({ address: null, value: new bn(0) }); + + // Change fee value if it is more than 1024 bytes + // (10000 satoshi for every 1024 bytes) + do { + // Calculate maximum possible size after signing + var byteSize = this.maxSize(); + + var addFee = Math.ceil(byteSize / 1024) - fee; + total.iadd(new bn(addFee * this.fee)); + fee += addFee; + + // Failed to get enough funds, add more inputs + if (this.funds('in').cmp(total) < 0) + unspent.slice(lastAdded).every(addInput, this); + } while (this.funds('in').cmp(total) < 0 && lastAdded < unspent.length); + + // Still failing to get enough funds + if (this.funds('in').cmp(total) < 0) { + this.inputs = inputs; + this.outputs.pop(); + this.cost = total; + return null; + } + + // How much money is left after sending outputs + var left = this.funds('in').sub(total); + + // Clear the tx of everything we added. + this.inputs = inputs; + this.outputs.pop(); + this.cost = total; + + // Return necessary utxos and change. + return { + utxos: utxos, + change: left, + cost: total + }; +}; + +TX.prototype.fillUnspent = function fillUnspent(unspent, change) { + var result = this.utxos(unspent); + + if (!result) + return result; + + result.utxos.forEach(function(utxo) { + this.input(utxo, null); + }, this); + + // Not enough money, transfer everything to owner + if (result.change.cmpn(this.dust) < 0) { + // NOTE: that this output is either `postCost` or one of the `dust` values + this.outputs[this.outputs.length - 1].value.iadd(result.change); + } else { + this.output({ + address: change || this.change, + value: result.change + }); + } + + return result; +}; + TX.prototype.inputAddrs = function inputAddrs() { return this.inputs.filter(function(input) { return bcoin.script.isPubkeyhashInput(input.script); diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index db20d8d9..2e03b69d 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -450,6 +450,14 @@ Wallet.prototype.fill = function fill(tx, cb) { return tx; }; +Wallet.prototype.fill = function fill(tx, cb) { + cb = utils.asyncify(cb); + tx.fillUnspent(this.unspent(), this.getAddress()); + this.sign(tx); + cb(null, tx); + return tx; +}; + // P2SH Multisig redeem script Wallet.prototype.getRedemption = function() { var sharedKeys = this.sharedKeys.slice().map(utils.toKeyArray); From 9241a6660e8da1727299db64b5e0dd6b6179d4f3 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 7 Dec 2015 11:02:50 -0800 Subject: [PATCH 25/59] remove old wallet.fill and wallet.sign. --- lib/bcoin/wallet.js | 136 -------------------------------------------- 1 file changed, 136 deletions(-) diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 2e03b69d..d7adaa78 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -270,67 +270,6 @@ Wallet.prototype.ownInput = function ownInput(tx, index) { return inputs; }; -Wallet.prototype.sign = function sign(tx, type, inputs, off) { - if (!type) - type = 'all'; - - if (!off) - off = 0; - - var pub = this.getPublicKey(); - inputs = inputs || tx.inputs; - - // Add signature script to each input - inputs = inputs.filter(function(input, i) { - // Filter inputs that this wallet own - if (!input.out.tx || !this.ownOutput(input.out.tx)) - return false; - - // Get the previous output's subscript - var s = input.out.tx.getSubscript(input.out.index); - - // Get the hash of the current tx, minus the other inputs, plus the sighash. - // `off` is used here in a case where we have multiple wallet objects - // signing the same tx. - var hash = tx.subscriptHash(off + i, s, type); - - // Sign the transaction with our one input - var signature = bcoin.ecdsa.sign(hash, this.key).toDER(); - - // Add the sighash as a single byte to the signature - signature = signature.concat(bcoin.protocol.constants.hashType[type]); - - // P2PKH and simple tx - if (bcoin.script.isPubkeyhash(s) || bcoin.script.isSimplePubkeyhash(s)) { - input.script = [ signature, pub ]; - return true; - } - - // Multisig - // empty array == OP_FALSE == OP_0 - // raw format: OP_FALSE [sig-1] [sig-2] ... - // p2sh format: OP_FALSE [sig-1] [sig-2] ... [redeem-script] - if (bcoin.script.isMultisig(s) || bcoin.script.isScripthash(s)) { - if (!input.script || !input.script.length) { - input.script = [ [], signature ]; - } else if (!~input.script.indexOf(signature)) { - input.script.push(signature); - } - } - - // P2SH requires a redeem script after signatures - if (bcoin.script.isScripthash(s)) { - if (input.script.length - 1 === this.m) { - input.script.push(this.getRedemption()); - } - } - - return true; - }, this); - - return inputs.length; -}; - Wallet.prototype.sign = function sign(tx, type, inputs) { if (!type) type = 'all'; @@ -375,81 +314,6 @@ Wallet.prototype.balance = function balance() { return this.tx.balance(); }; -Wallet.prototype.fill = function fill(tx, cb) { - cb = utils.asyncify(cb); - - // NOTE: tx should be prefilled with all outputs - var cost = tx.funds('out'); - - // Use initial fee for starters - var fee = 1; - - // total = cost + fee - var total = cost.add(new bn(this.fee)); - - var lastAdded = 0; - function addInput(unspent, i) { - // Add new inputs until TX will have enough funds to cover both - // minimum post cost and fee - tx.input(unspent); - lastAdded++; - return tx.funds('in').cmp(total) < 0; - } - - // Transfer `total` funds maximum - var unspent = this.unspent(); - unspent.every(addInput, this); - - // Add dummy output (for `left`) to calculate maximum TX size - tx.out(this, new bn(0)); - - // Change fee value if it is more than 1024 bytes - // (10000 satoshi for every 1024 bytes) - do { - // Calculate maximum possible size after signing - var byteSize = tx.maxSize(); - - var addFee = Math.ceil(byteSize / 1024) - fee; - total.iadd(new bn(addFee * this.fee)); - fee += addFee; - - // Failed to get enough funds, add more inputs - if (tx.funds('in').cmp(total) < 0) - unspent.slice(lastAdded).every(addInput, this); - } while (tx.funds('in').cmp(total) < 0 && lastAdded < unspent.length); - - // Still failing to get enough funds, notify caller - if (tx.funds('in').cmp(total) < 0) { - var err = new Error('Not enough funds'); - err.minBalance = total; - return cb(err); - } - - // How much money is left after sending outputs - var left = tx.funds('in').sub(total); - - // Not enough money, transfer everything to owner - if (left.cmpn(this.dust) < 0) { - // NOTE: that this output is either `postCost` or one of the `dust` values - tx.outputs[tx.outputs.length - 2].value.iadd(left); - left = new bn(0); - } - - // Change or remove last output if there is some money left - if (left.cmpn(0) === 0) - tx.outputs.pop(); - else - tx.outputs[tx.outputs.length - 1].value = left; - - // Sign transaction - // XXX Do not necessarily call this here - this.sign(tx); - - cb(null, tx); - - return tx; -}; - Wallet.prototype.fill = function fill(tx, cb) { cb = utils.asyncify(cb); tx.fillUnspent(this.unspent(), this.getAddress()); From 3d9d6be23c3cc3edd8fc7caef830d1e74fc60253 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 7 Dec 2015 11:04:23 -0800 Subject: [PATCH 26/59] use this.m for nsigs. --- lib/bcoin/tx.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index a1523c80..2d77ca7e 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -42,8 +42,8 @@ function TX(data, block) { // ps = Pending Since this.ps = this.ts === 0 ? +new Date() / 1000 : 0; - this.m = data.m || 1; - this.n = data.n || 1; + this.m = data.m; + this.n = data.n; this.change = data.change || null; this.fee = data.fee || 10000; this.dust = 5460; @@ -153,6 +153,7 @@ TX.prototype.scriptInput = function(input, pub, nsigs) { // Multisig // raw format: OP_FALSE [sig-1] [sig-2] ... if (bcoin.script.isMultisig(s)) { + nsigs = nsigs || this.m; if (!nsigs) throw new Error('`nsigs` is required for multisig'); input.script = [ constants.opcodes['false'] ]; From 1804acc77ce1bd5d506bf34482da1b09b41ea6e9 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 7 Dec 2015 12:28:50 -0800 Subject: [PATCH 27/59] tests passing. --- lib/bcoin/script.js | 22 ++++++++++++++++++++-- lib/bcoin/tx.js | 33 ++++++++++++++++++++------------- lib/bcoin/wallet.js | 8 +++++++- test/wallet-test.js | 2 +- 4 files changed, 48 insertions(+), 17 deletions(-) diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 166dad63..ecd21535 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -110,6 +110,24 @@ script.subscript = function subscript(s) { return res; }; +script.verify = function verify(hash, sig, pub) { + var k = bcoin.ecdsa.keyFromPublic(pub); + + // Points at Infinity make verify() throw. + // This specifically throws on wallet-test.js + // where [1] is concatted to the pubkey. + if (k.getPublic().isInfinity()) + return false; + + // Use a try catch in case there are + // any uncaught errors for bad inputs in verify(). + try { + return bcoin.ecdsa.verify(hash, sig, pub); + } catch (e) { + return false; + } +}; + script.execute = function execute(s, stack, tx) { if (s.length > 10000) { return false; @@ -155,7 +173,7 @@ script.execute = function execute(s, stack, tx) { // if (typeof tx === 'function') // tx = tx(constants.rhashType[type]); - var res = bcoin.ecdsa.verify(tx, sig.slice(0, -1), pub); + var res = script.verify(tx, sig.slice(0, -1), pub); if (o === 'checksigverify') { if (!res) return false; @@ -205,7 +223,7 @@ script.execute = function execute(s, stack, tx) { var res = false; for (; !res && j < n; j++) - res = bcoin.ecdsa.verify(tx, sig.slice(0, -1), keys[j]); + res = script.verify(tx, sig.slice(0, -1), keys[j]); if (res) succ++; } diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 2d77ca7e..e1f879dd 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -42,8 +42,8 @@ function TX(data, block) { // ps = Pending Since this.ps = this.ts === 0 ? +new Date() / 1000 : 0; - this.m = data.m; - this.n = data.n; + this.m = data.m || null; + this.n = data.n || null; this.change = data.change || null; this.fee = data.fee || 10000; this.dust = 5460; @@ -146,7 +146,8 @@ TX.prototype.scriptInput = function(input, pub, nsigs) { // P2PKH and simple tx if (bcoin.script.isPubkeyhash(s) || bcoin.script.isSimplePubkeyhash(s)) { - input.script = [ constants.opcodes['0'], pub ]; + //input.script = [ constants.opcodes['0'], pub ]; + input.script = [ [], pub ]; return; } @@ -156,19 +157,23 @@ TX.prototype.scriptInput = function(input, pub, nsigs) { nsigs = nsigs || this.m; if (!nsigs) throw new Error('`nsigs` is required for multisig'); - input.script = [ constants.opcodes['false'] ]; + //input.script = [ constants.opcodes['false'] ]; + input.script = [ [] ]; for (var i = 0; i < nsigs; i++) - input.script[i + 1] = constants.opcodes['0']; + //input.script[i + 1] = constants.opcodes['0']; + input.script[i + 1] = []; return; } // P2SH multisig // p2sh format: OP_FALSE [sig-1] [sig-2] ... [redeem-script] if (bcoin.script.isScripthash(s)) { - input.script = [ constants.opcodes['false'] ]; + //input.script = [ constants.opcodes['false'] ]; + input.script = [ [] ]; var m = pub[0] - constants.opcodes['1'] + 1; for (var i = 0; i < m; i++) - input.script[i + 1] = constants.opcodes['0']; + //input.script[i + 1] = constants.opcodes['0']; + input.script[i + 1] = []; // P2SH requires the redeem script after signatures input.script.push(pub); return; @@ -186,7 +191,7 @@ TX.prototype.signInput = function(input, key, type) { var s = input.out.tx.getSubscript(input.out.index); // Get the hash of the current tx, minus the other inputs, plus the sighash. - var hash = this.subscriptHash(tx.inputs.indexOf(input), s, type); + var hash = this.subscriptHash(this.inputs.indexOf(input), s, type); // Sign the transaction with our one input var signature = bcoin.ecdsa.sign(hash, key).toDER(); @@ -214,7 +219,8 @@ TX.prototype.signInput = function(input, key, type) { if (utils.isEqual(input.script[i], signature)) break; - if (input.script[i] === constants.opcodes['0']) { + //if (input.script[i] === constants.opcodes['0']) { + if (input.script[i].length === 0) { input.script[i] = signature; break; } @@ -228,10 +234,10 @@ TX.prototype.signInput = function(input, key, type) { // Build the scriptSig and sign it TX.prototype.scriptSig = function(input, key, pub, type, nsigs) { // Build script for input - tx.scriptInput(input, pub, nsigs); + this.scriptInput(input, pub, nsigs); // Sign input - tx.signInput(input, key, type); + this.signInput(input, key, type); return input.script; }; @@ -251,13 +257,15 @@ TX.prototype.output = function output(output, value) { value: new bn(output.value), script: this.scriptOutput(output) }); + + return this; }; // compat TX.prototype.out = TX.prototype.output; TX.prototype.scriptOutput = function(options) { - var script = []; + var script = options.script ? options.script.slice() : []; if (Array.isArray(options.keys || options.address)) { // Raw multisig transaction @@ -360,7 +368,6 @@ TX.prototype.verify = function verify(index, force) { if (!res) return false; - // XXX sighash_all here? return stack.length > 0 && utils.isEqual(stack.pop(), [ 1 ]); }, this); }; diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index d7adaa78..6781f5b7 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -316,7 +316,13 @@ Wallet.prototype.balance = function balance() { Wallet.prototype.fill = function fill(tx, cb) { cb = utils.asyncify(cb); - tx.fillUnspent(this.unspent(), this.getAddress()); + var result = tx.fillUnspent(this.unspent(), this.getAddress()); + if (!result) { + var err = new Error('Not enough funds'); + err.minBalance = tx.cost; + cb(err); + return null; + } this.sign(tx); cb(null, tx); return tx; diff --git a/test/wallet-test.js b/test/wallet-test.js index e244bba3..bf48ce63 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -52,7 +52,7 @@ describe('Wallet', function() { outputs: [{ value: 5460 * 2, minSignatures: 1, - address: [ w.getPublicKey(), w.getPublicKey().concat(1) ] + keys: [ w.getPublicKey(), w.getPublicKey().concat(1) ] }, { value: 5460 * 2, address: w.getAddress() + 'x' From cf20326e98d8c6356a2260295c6005268b98c292 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 7 Dec 2015 14:12:42 -0800 Subject: [PATCH 28/59] verify sighash types other than `all`. --- lib/bcoin/script.js | 26 +++++++++++++------------- lib/bcoin/tx.js | 27 +++++++++++++++++---------- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index ecd21535..d0785fac 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -128,7 +128,7 @@ script.verify = function verify(hash, sig, pub) { } }; -script.execute = function execute(s, stack, tx) { +script.execute = function execute(s, stack, hasher) { if (s.length > 10000) { return false; } @@ -160,20 +160,20 @@ script.execute = function execute(s, stack, tx) { } } else if (o === 'checksigverify' || o === 'checksig') { - if (!tx || stack.length < 2) + if (!hasher || stack.length < 2) return false; var pub = stack.pop(); var sig = stack.pop(); var type = sig[sig.length - 1]; - if (type !== 1) + if (!constants.rhashType[type & 0x7f]) return false; - // XXX Deal with different hashTypes besides `all` - // if (typeof tx === 'function') - // tx = tx(constants.rhashType[type]); + var hash = hasher; + if (typeof hash === 'function') + hash = hash(type); - var res = script.verify(tx, sig.slice(0, -1), pub); + var res = script.verify(hash, sig.slice(0, -1), pub); if (o === 'checksigverify') { if (!res) return false; @@ -181,7 +181,7 @@ script.execute = function execute(s, stack, tx) { stack.push(res ? [ 1 ] : []); } } else if (o === 'checkmultisigverify' || o === 'checkmultisig') { - if (!tx || stack.length < 3) + if (!hasher || stack.length < 3) return false; var n = stack.pop(); @@ -214,16 +214,16 @@ script.execute = function execute(s, stack, tx) { for (var i = 0, j = 0; i < m && j < n; i++) { var sig = stack.pop(); var type = sig[sig.length - 1]; - if (type !== 1) + if (!constants.rhashType[type & 0x7f]) return false; - // XXX Deal with different hashTypes besides `all` - // if (typeof tx === 'function') - // tx = tx(constants.rhashType[type]); + var hash = hasher; + if (typeof hash === 'function') + hash = hash(type); var res = false; for (; !res && j < n; j++) - res = script.verify(tx, sig.slice(0, -1), keys[j]); + res = script.verify(hash, sig.slice(0, -1), keys[j]); if (res) succ++; } diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index e1f879dd..74d9cea3 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -124,6 +124,12 @@ TX.prototype._inputIndex = function _inputIndex(hash, index) { }; TX.prototype.signature = function(input, key, type) { + if (!type) + type = 'all'; + + if (typeof type === 'string') + type = bcoin.protocol.constants.hashType[type]; + // Get the previous output's subscript var s = input.out.tx.getSubscript(input.out.index); @@ -134,7 +140,7 @@ TX.prototype.signature = function(input, key, type) { var signature = bcoin.ecdsa.sign(hash, key).toDER(); // Add the sighash as a single byte to the signature - signature = signature.concat(constants.hashType[type]); + signature = signature.concat(type); return signature; }; @@ -187,6 +193,9 @@ TX.prototype.signInput = function(input, key, type) { if (!type) type = 'all'; + if (typeof type === 'string') + type = bcoin.protocol.constants.hashType[type]; + // Get the previous output's subscript var s = input.out.tx.getSubscript(input.out.index); @@ -197,7 +206,7 @@ TX.prototype.signInput = function(input, key, type) { var signature = bcoin.ecdsa.sign(hash, key).toDER(); // Add the sighash as a single byte to the signature - signature = signature.concat(constants.hashType[type]); + signature = signature.concat(type); // P2PKH and simple tx if (bcoin.script.isPubkeyhash(s) || bcoin.script.isSimplePubkeyhash(s)) { @@ -329,13 +338,14 @@ TX.prototype.getSubscript = function getSubscript(index) { TX.prototype.subscriptHash = function subscriptHash(index, s, type) { var copy = this.clone(); + if (typeof type === 'string') + type = bcoin.protocol.constants.hashType[type]; + copy.inputs.forEach(function(input, i) { input.script = index === i ? s : []; }); var verifyStr = copy.render(); - verifyStr = verifyStr.concat( - bcoin.protocol.constants.hashType[type], 0, 0, 0 - ); + utils.writeU32(verifyStr, type, verifyStr.length); var hash = utils.dsha256(verifyStr); return hash; @@ -356,15 +366,12 @@ TX.prototype.verify = function verify(index, force) { assert(input.out.tx.outputs.length > input.out.index); var subscript = input.out.tx.getSubscript(input.out.index); - var hash = this.subscriptHash(i, subscript, 'all'); - - // XXX Deal with different hashTypes besides `all` - // var hash = this.subscriptHash.bind(this, i, subscript); + var hasher = this.subscriptHash.bind(this, i, subscript); var stack = []; bcoin.script.execute(input.script, stack); var prev = input.out.tx.outputs[input.out.index].script; - var res = bcoin.script.execute(prev, stack, hash); + var res = bcoin.script.execute(prev, stack, hasher); if (!res) return false; From b1e7cde3dde2c378959bbc424e5a7b9bb980adf9 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 7 Dec 2015 15:39:18 -0800 Subject: [PATCH 29/59] improve script functionality and usage. --- lib/bcoin/script.js | 46 +++++++++++++++++----------- lib/bcoin/tx.js | 73 ++++++++++++++++++++++++++++----------------- lib/bcoin/wallet.js | 30 +++++++++---------- 3 files changed, 90 insertions(+), 59 deletions(-) diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index d0785fac..8db84dd4 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -1,6 +1,7 @@ var bcoin = require('../bcoin'); var constants = bcoin.protocol.constants; var utils = bcoin.utils; +var assert = bcoin.utils.assert; var script = exports; script.decode = function decode(s) { @@ -129,14 +130,15 @@ script.verify = function verify(hash, sig, pub) { }; script.execute = function execute(s, stack, hasher) { - if (s.length > 10000) { + if (s.length > 10000) return false; - } for (var pc = 0; pc < s.length; pc++) { var o = s[pc]; if (Array.isArray(o)) { stack.push(o); + } else if (typeof o === 'number' && o >= 1 && o <= 16) { + stack.push([o]); } else if (o === 'dup') { if (stack.length === 0) return false; @@ -247,21 +249,27 @@ script.execute = function execute(s, stack, hasher) { return true; }; -script.redemption = function(publicKeys, m, n) { - if (publicKeys.length !== m) { - throw new Error('wrong amount of pubkeys for redeem script'); - } - var mcode = constants.opcodes['1'] + (m - 1); - var ncode = constants.opcodes['1'] + (n - 1); - var redemption = []; - redemption.push(mcode); - publicKeys.forEach(function(pubkey) { - redemption.push(pubkey.length); - redemption = redemption.concat(pubkey); - }); - redemption.push(ncode); - redemption.push(constants.opcodes.checkmultisig); - return redemption; +script.multisig = function(keys, m, n) { + if (keys.length < m) + throw new Error('wrong amount of pubkeys for multisig script'); + + assert(m >= 1 && m <= n); + assert(n >= 1 && n <= 7); + + // Format: + // op_[m] [pubkey1-len] [pubkey1] ... op_[n] op_checkmultisig + + // Using pushdata ops for m and n: + // return [ [ m ] ].concat( + // keys, + // [ [ n ], 'checkmultisig' ] + // ); + + // Using OP_1-16 for m and n: + return [ m ].concat( + keys, + [ n, 'checkmultisig' ] + ); }; script.isPubkeyhash = function isPubkeyhash(s, hash) { @@ -301,6 +309,8 @@ script.isMultisig = function isMultisig(s, key) { return false; var m = s[0]; + if (typeof m === 'number' && m >= 1 && m <= 16) + m = [m]; if (!Array.isArray(m) || m.length !== 1) return false; m = m[0]; @@ -309,6 +319,8 @@ script.isMultisig = function isMultisig(s, key) { return false; var n = s[s.length - 2]; + if (typeof n === 'number' && n >= 1 && n <= 16) + n = [n]; if (!Array.isArray(n) || n.length !== 1) return false; n = n[0]; diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 74d9cea3..c8bdf063 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -42,8 +42,6 @@ function TX(data, block) { // ps = Pending Since this.ps = this.ts === 0 ? +new Date() / 1000 : 0; - this.m = data.m || null; - this.n = data.n || null; this.change = data.change || null; this.fee = data.fee || 10000; this.dust = 5460; @@ -152,7 +150,6 @@ TX.prototype.scriptInput = function(input, pub, nsigs) { // P2PKH and simple tx if (bcoin.script.isPubkeyhash(s) || bcoin.script.isSimplePubkeyhash(s)) { - //input.script = [ constants.opcodes['0'], pub ]; input.script = [ [], pub ]; return; } @@ -160,13 +157,12 @@ TX.prototype.scriptInput = function(input, pub, nsigs) { // Multisig // raw format: OP_FALSE [sig-1] [sig-2] ... if (bcoin.script.isMultisig(s)) { - nsigs = nsigs || this.m; - if (!nsigs) - throw new Error('`nsigs` is required for multisig'); - //input.script = [ constants.opcodes['false'] ]; input.script = [ [] ]; - for (var i = 0; i < nsigs; i++) - //input.script[i + 1] = constants.opcodes['0']; + var m = s[0]; + // If using pushdata instead of OP_1-16: + if (Array.isArray(m)) + m = m[0]; + for (var i = 0; i < m; i++) input.script[i + 1] = []; return; } @@ -174,11 +170,9 @@ TX.prototype.scriptInput = function(input, pub, nsigs) { // P2SH multisig // p2sh format: OP_FALSE [sig-1] [sig-2] ... [redeem-script] if (bcoin.script.isScripthash(s)) { - //input.script = [ constants.opcodes['false'] ]; input.script = [ [] ]; var m = pub[0] - constants.opcodes['1'] + 1; for (var i = 0; i < m; i++) - //input.script[i + 1] = constants.opcodes['0']; input.script[i + 1] = []; // P2SH requires the redeem script after signatures input.script.push(pub); @@ -228,7 +222,6 @@ TX.prototype.signInput = function(input, key, type) { if (utils.isEqual(input.script[i], signature)) break; - //if (input.script[i] === constants.opcodes['0']) { if (input.script[i].length === 0) { input.script[i] = signature; break; @@ -283,26 +276,42 @@ TX.prototype.scriptOutput = function(options) { // https://github.com/bitcoin/bips/blob/master/bip-0019.mediawiki // [required-sigs] [pubkey-hash1] [pubkey-hash2] ... [number-of-keys] checkmultisig var keys = options.keys || options.address; + if (keys === options.address) { keys = keys.map(function(address) { return bcoin.wallet.addr2hash(address, 'normal'); }); } + keys = keys.map(function(key) { if (typeof key === 'string') { return utils.toKeyArray(key); } return key; }); - script = [ - [ options.minSignatures || keys.length ] - ].concat( - keys, - [ [ keys.length ], 'checkmultisig' ] - ); - // outputs: [ [ 2 ], 'key1', 'key2', [ 2 ], 'checkmultisig' ] - // in reality: - // outputs: [ [ 2 ], [0,1,...], [2,3,...], [ 2 ], 'checkmultisig' ] + + // compat: + options.m = options.minSignatures || options.m; + var m = options.m || keys.length; + var n = options.n || keys.length; + + assert(m >= 1 && m <= n); + if (options.hash) + assert(n >= 1 && n <= 7); + else + assert(n >= 1 && n <= 3); + + script = bcoin.script.multisig(keys, m, n); + + // make it p2sh + if (options.hash) { + var hash = utils.ripesha(bcoin.script.encode(script)); + script = [ + 'hash160', + hash, + 'eq' + ]; + } } else if (bcoin.wallet.validateAddress(options.address, 'p2sh')) { // p2sh transaction // https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki @@ -413,8 +422,11 @@ TX.prototype.maxSize = function maxSize() { // Empty byte size += 1; // Signature + len - var m = s[0][0] || this.m; - assert(m >= 1 && m <= 7); + var m = s[0]; + // If using pushdata instead of OP_1-16: + if (Array.isArray(m)) + m = m[0]; + assert(m >= 1 && m <= 3); size += 74 * m; return; } @@ -424,11 +436,18 @@ TX.prototype.maxSize = function maxSize() { var redeem, m, n; if (script) { redeem = script[script.length - 1]; - m = redeem[0] - constants.opcodes['1'] + 1; - n = redeem[redeem.length - 2] - constants.opcodes['1'] + 1; + m = redeem[0]; + n = redeem[redeem.length - 2]; + // If using pushdata instead of OP_1-16: + if (Array.isArray(m)) + m = m[0]; + if (Array.isArray(n)) + n = n[0]; } else { - m = this.m; - n = this.n; + // May end up in a higher fee if we + // do not have the redeem script available. + m = 7; + n = 7; } assert(m >= 1 && m <= n); assert(n >= 1 && n <= 7); diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 6781f5b7..afe2d1e4 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -149,11 +149,12 @@ Wallet.prototype.getPrivateKey = function getPrivateKey(enc) { }; Wallet.prototype.getPublicKey = function getPublicKey(enc) { - var pub; - if (this.addressType === 'p2sh') - pub = this.getRedemption(); - else - pub = this.key.getPublic(this.compressed, 'array'); + var pub = this.key.getPublic(this.compressed, 'array'); + + if (this.addressType === 'p2sh') { + var keys = this.getPublicKeys(); + pub = bcoin.script.encode(bcoin.script.multisig(keys, m, n)); + } if (enc === 'base58') return utils.toBase58(pub); @@ -163,6 +164,15 @@ Wallet.prototype.getPublicKey = function getPublicKey(enc) { return pub; }; +Wallet.prototype.getPublicKeys = function() { + var keys = this.sharedKeys.slice().map(utils.toKeyArray); + if (keys.length < this.m) { + var pub = this.key.getPublic(this.compressed, 'array'); + keys.push(pub); + } + return keys; +}; + Wallet.prototype.getHash = function getHash() { return utils.ripesha(this.getPublicKey()); }; @@ -328,16 +338,6 @@ Wallet.prototype.fill = function fill(tx, cb) { return tx; }; -// P2SH Multisig redeem script -Wallet.prototype.getRedemption = function() { - var sharedKeys = this.sharedKeys.slice().map(utils.toKeyArray); - if (sharedKeys.length < this.m) { - var pub = this.key.getPublic(this.compressed, 'array'); - sharedKeys.push(pub); - } - return bcoin.script.redemption(sharedKeys, m, n); -}; - Wallet.prototype.toJSON = function toJSON() { return { v: 1, From 37e1c8a2efd3b24ecb9149140c498c93f0f0357d Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 7 Dec 2015 15:51:15 -0800 Subject: [PATCH 30/59] formatting changes. --- lib/bcoin/hd.js | 167 ++++++++++++++++++++++++------------------------ lib/bcoin/tx.js | 3 +- 2 files changed, 83 insertions(+), 87 deletions(-) diff --git a/lib/bcoin/hd.js b/lib/bcoin/hd.js index bf8975cc..aa637ad4 100644 --- a/lib/bcoin/hd.js +++ b/lib/bcoin/hd.js @@ -68,9 +68,9 @@ var ec = elliptic.curves.secp256k1; */ function HDSeed(options) { - if (!(this instanceof HDSeed)) { + if (!(this instanceof HDSeed)) return new HDSeed(options); - } + this.bits = options.bits || 128; this.entropy = options.entropy || HDSeed._entropy(this.bits / 8); this.mnemonic = options.mnemonic || HDSeed._mnemonic(this.entropy); @@ -131,21 +131,17 @@ var PATH_ROOTS = ['m', 'M', 'm\'', 'M\'']; function HDPriv(options) { var data; - if (!(this instanceof HDPriv)) { + if (!(this instanceof HDPriv)) return new HDPriv(options); - } - if (!options) { + if (!options) options = { seed: new HDSeed({ passphrase: '' }) }; - } - if (typeof options === 'string' && options.indexOf('xprv') === 0) { + if (typeof options === 'string' && options.indexOf('xprv') === 0) options = { xkey: options }; - } - if (options.passphrase) { + if (options.passphrase) options.seed = new HDSeed({ passphrase: options.passphrase }); - } if (options.seed) { if (typeof options.seed === 'object' && !(options.seed instanceof HDSeed)) { @@ -168,53 +164,60 @@ function HDPriv(options) { HDPriv.prototype._normalize = function(data, version) { var b; + data.version = version || VERSION.xprivkey; data.version = +data.version; + data.depth = +data.depth; + if (typeof data.parentFingerPrint === 'number') { b = []; utils.writeU32BE(b, data.parentFingerPrint, 0); data.parentFingerPrint = b; } + data.childIndex = +data.childIndex; - if (typeof data.chainCode === 'string') { + + if (typeof data.chainCode === 'string') data.chainCode = utils.toArray(data.chainCode, 'hex'); - } + data.privateKey = data.privateKey || data.priv; if (data.privateKey) { - if (data.privateKey.getPrivate) { + if (data.privateKey.getPrivate) data.privateKey = data.privateKey.getPrivate().toArray(); - } else if (typeof data.privateKey === 'string') { + else if (typeof data.privateKey === 'string') data.privateKey = utils.toArray(data.privateKey, 'hex'); - } } + data.publicKey = data.publicKey || data.pub; if (data.publicKey) { - if (data.publicKey.getPublic) { + if (data.publicKey.getPublic) data.publicKey = data.privateKey.getPublic(true, 'array'); - } else if (typeof data.publicKey === 'string') { + else if (typeof data.publicKey === 'string') data.publicKey = utils.toArray(data.publicKey, 'hex'); - } } + if (typeof data.checksum === 'number') { b = []; utils.writeU32BE(b, data.checksum, 0); data.checksum = b; } + return data; }; HDPriv.prototype._seed = function(seed) { - if (seed instanceof HDSeed) { + if (seed instanceof HDSeed) seed = seed.seed; - } - if (typeof seed === 'string' && /^[0-9a-f]+$/i.test(seed)) { + + if (typeof seed === 'string' && /^[0-9a-f]+$/i.test(seed)) seed = utils.toArray(seed, 'hex'); - } - if (seed.length < MIN_ENTROPY || seed.length > MAX_ENTROPY) { - throw new Error; - } + + if (seed.length < MIN_ENTROPY || seed.length > MAX_ENTROPY) + throw new Error('entropy not in range'); + var hash = sha512hmac(seed, 'Bitcoin seed'); + return { // version: VERSION.xprivkey, depth: 0, @@ -230,6 +233,7 @@ HDPriv.prototype._unbuild = function(xkey) { var raw = utils.fromBase58(xkey); var data = {}; var off = 0; + data.version = utils.readU32BE(raw, off); off += 4; data.depth = raw[off]; @@ -245,16 +249,18 @@ HDPriv.prototype._unbuild = function(xkey) { off += data.privateKey.length; data.checksum = utils.readU32BE(raw, off); off += 4; + var hash = utils.dsha256(raw.slice(0, -4)); - if (data.checksum !== utils.readU32BE(hash, 0)) { + if (data.checksum !== utils.readU32BE(hash, 0)) throw new Error('checksum mismatch'); - } + return data; }; HDPriv.prototype._build = function(data) { var sequence = []; var off = 0; + utils.writeU32BE(sequence, data.version, off); off += 4; sequence[off] = data.depth; @@ -300,24 +306,22 @@ HDPriv.prototype._build = function(data) { }; HDPriv.prototype.derive = function(index, hard) { - if (typeof index === 'string') { + if (typeof index === 'string') return this.deriveString(index); - } hard = index >= HARDENED ? true : hard; - if (index < HARDENED && hard === true) { + if (index < HARDENED && hard === true) index += HARDENED; - } var index_ = []; utils.writeU32BE(index_, index, 0); var data; - if (hard) { + if (hard) data = [0].concat(this.privateKey).concat(index_); - } else { + else data = [].concat(this.publicKey).concat(index_); - } + var hash = sha512hmac(data, this.chainCode); var leftPart = new bn(hash.slice(0, 32)); var chainCode = hash.slice(32, 64); @@ -327,7 +331,10 @@ HDPriv.prototype.derive = function(index, hard) { //var privateKey = leftPart.add(new bn(this.privateKey)).mod(ec.curve.n).toArray(); // Use this as a workaround: - var n = new bn('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', 'hex'); + var n = new bn( + 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', + 'hex' + ); var privateKey = leftPart.add(new bn(this.privateKey)).mod(n).toArray(); return new HDPriv({ @@ -346,27 +353,26 @@ HDPriv._getIndexes = function(path) { var root = steps.shift(); var indexes = []; - if (~PATH_ROOTS.indexOf(path)) { + if (~PATH_ROOTS.indexOf(path)) return indexes; - } - if (!~PATH_ROOTS.indexOf(root)) { + if (!~PATH_ROOTS.indexOf(root)) return null; - } for (var i = 0; i < steps.length; i++) { var step = steps[i]; var hard = step[step.length - 1] === '\''; - if (hard) { + + if (hard) step = step.slice(0, -1); - } - if (!step || step[0] === '-') { + + if (!step || step[0] === '-') return null; - } + var index = +step; - if (hard) { + if (hard) index += HARDENED; - } + indexes.push(index); } @@ -390,9 +396,8 @@ HDPriv.isValidPath = function(path, hard) { }; HDPriv.prototype.deriveString = function(path) { - if (!HDPriv.isValidPath(path)) { + if (!HDPriv.isValidPath(path)) throw new Error('invalid path'); - } var indexes = HDPriv._getIndexes(path); @@ -406,21 +411,18 @@ HDPriv.prototype.deriveString = function(path) { */ function HDPub(options) { - if (!(this instanceof HDPub)) { + if (!(this instanceof HDPub)) return new HDPriv(options); - } var data; - if (typeof options === 'string' && options.indexOf('xpub') === 0) { + if (typeof options === 'string' && options.indexOf('xpub') === 0) options = { xkey: options }; - } - if (options.xkey) { + if (options.xkey) data = this._unbuild(options.xkey); - } else { + else data = options; - } data = this._normalize(data, VERSION.xpubkey); @@ -435,6 +437,7 @@ HDPub.prototype._unbuild = function(xkey) { var raw = utils.fromBase58(xkey); var data = {}; var off = 0; + data.version = utils.readU32BE(raw, off); off += 4; data.depth = raw[off]; @@ -449,16 +452,18 @@ HDPub.prototype._unbuild = function(xkey) { off += data.publicKey.length; data.checksum = utils.readU32BE(raw, off); off += 4; + var hash = utils.dsha256(raw.slice(0, -4)); - if (data.checksum !== utils.readU32BE(hash, 0)) { + if (data.checksum !== utils.readU32BE(hash, 0)) throw new Error('checksum mismatch'); - } + return data; }; HDPub.prototype._build = function(data) { var sequence = []; var off = 0; + utils.writeU32BE(sequence, data.version, off); off += 4; sequence[off] = data.depth; @@ -475,11 +480,10 @@ HDPub.prototype._build = function(data) { utils.copy(checksum, sequence, off, true); off += 4; - if (!data.checksum || !data.checksum.length) { + if (!data.checksum || !data.checksum.length) data.checksum = checksum; - } else if (utils.toHex(checksum) !== utils.toHex(data.checksum)) { - throw new Error; - } + else if (utils.toHex(checksum) !== utils.toHex(data.checksum)) + throw new Error('checksum mismatch'); var xpubkey = utils.toBase58(sequence); @@ -503,16 +507,14 @@ HDPub.prototype._build = function(data) { }; HDPub.prototype.derive = function(index, hard) { - if (typeof index === 'string') { + if (typeof index === 'string') return this.deriveString(index); - } - if (index >= HARDENED || hard) { - throw new Error; - } - if (index < 0) { - throw new Error; - } + if (index >= HARDENED || hard) + throw new Error('invalid index'); + + if (index < 0) + throw new Error('invalid path'); var index_ = []; utils.writeU32BE(index_, index, 0); @@ -541,18 +543,18 @@ HDPub.isValidPath = function(arg) { var indexes = HDPriv._getIndexes(arg); return indexes !== null && indexes.every(HDPub.isValidPath); } - if (typeof arg === 'number') { + + if (typeof arg === 'number') return arg >= 0 && arg < HARDENED; - } + return false; }; HDPub.prototype.deriveString = function(path) { - if (~path.indexOf('\'')) { + if (~path.indexOf('\'')) throw new Error('cannot derive hardened'); - } else if (!HDPub.isValidPath(path)) { + else if (!HDPub.isValidPath(path)) throw new Error('invalid path'); - } var indexes = HDPriv._getIndexes(path); @@ -601,25 +603,20 @@ function pbkdf2(key, salt, iterations, dkLen) { 'use strict'; var hLen = 64; - if (dkLen > (Math.pow(2, 32) - 1) * hLen) { + if (dkLen > (Math.pow(2, 32) - 1) * hLen) throw Error('Requested key length too long'); - } - if (typeof key !== 'string' && typeof key.length !== 'number') { + if (typeof key !== 'string' && typeof key.length !== 'number') throw new TypeError('key must a string or array'); - } - if (typeof salt !== 'string' && typeof salt.length !== 'number') { + if (typeof salt !== 'string' && typeof salt.length !== 'number') throw new TypeError('salt must a string or array'); - } - if (typeof key === 'string') { + if (typeof key === 'string') key = utils.toArray(key, null); - } - if (typeof salt === 'string') { + if (typeof salt === 'string') salt = utils.toArray(salt, null); - } var DK = new Array(dkLen); var U = new Array(hLen); @@ -630,6 +627,7 @@ function pbkdf2(key, salt, iterations, dkLen) { var r = dkLen - (l - 1) * hLen; utils.copy(salt.slice(0, salt.length), block1, 0); + for (var i = 1; i <= l; i++) { block1[salt.length + 0] = (i >> 24 & 0xff); block1[salt.length + 1] = (i >> 16 & 0xff); @@ -643,9 +641,8 @@ function pbkdf2(key, salt, iterations, dkLen) { for (var j = 1; j < iterations; j++) { U = sha512hmac(U, key); - for (var k = 0; k < hLen; k++) { + for (var k = 0; k < hLen; k++) T[k] ^= U[k]; - } } var destPos = (i - 1) * hLen; diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index c8bdf063..e57f17a6 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -284,9 +284,8 @@ TX.prototype.scriptOutput = function(options) { } keys = keys.map(function(key) { - if (typeof key === 'string') { + if (typeof key === 'string') return utils.toKeyArray(key); - } return key; }); From a1fea2bd65b3fecd84f8b00301cfc29ff1e4b09a Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 7 Dec 2015 15:53:56 -0800 Subject: [PATCH 31/59] scriptOutput: move "p2shify". --- lib/bcoin/tx.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index e57f17a6..bab63608 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -301,16 +301,6 @@ TX.prototype.scriptOutput = function(options) { assert(n >= 1 && n <= 3); script = bcoin.script.multisig(keys, m, n); - - // make it p2sh - if (options.hash) { - var hash = utils.ripesha(bcoin.script.encode(script)); - script = [ - 'hash160', - hash, - 'eq' - ]; - } } else if (bcoin.wallet.validateAddress(options.address, 'p2sh')) { // p2sh transaction // https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki @@ -332,6 +322,18 @@ TX.prototype.scriptOutput = function(options) { ]; } + // make it p2sh + if (options.hash) { + var redeem = script; + var hash = utils.ripesha(bcoin.script.encode(redeem)); + script = [ + 'hash160', + hash, + 'eq' + ]; + script.redeem = redeem; + } + return script; }; From 58d8881e46cd7374240786ba98a65eebd1b3063d Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 7 Dec 2015 16:17:09 -0800 Subject: [PATCH 32/59] add hd tests. --- lib/bcoin/hd.js | 89 +-------------------------------------------- test/hd-test.js | 95 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 88 deletions(-) create mode 100644 test/hd-test.js diff --git a/lib/bcoin/hd.js b/lib/bcoin/hd.js index aa637ad4..4225356d 100644 --- a/lib/bcoin/hd.js +++ b/lib/bcoin/hd.js @@ -660,91 +660,4 @@ function pbkdf2(key, salt, iterations, dkLen) { hd.seed = HDSeed; hd.priv = HDPriv; hd.pub = HDPub; - -/** - * Test - */ - -var phrase = 'volume doll flush federal inflict tomato result property total curtain shield aisle'; -var checkSeed = utils.toHex(pbkdf2(phrase, 'mnemonic' + 'foo', 2048, 64)); -var seed = '5559092716434b83f158bffb51337a944529ae30d7e62d46d3be0c66fa4b36e8d60ccfd2c976b831885dc9df9ac3716ee4bf90003f25621070a49cbea58f528b'; -assert.equal(checkSeed, seed); - -var master_priv = 'xprv9s21ZrQH143K35zTejeVRhkXgegDFUVpoh8Mxs2BQmXEB4w9SZ1CuoJPuQ2KGQrS1ZF3Pk7V7KWHn7FqR2JbAE9Bh8PURnrFnrmArj4kxos'; -var master_pub = 'xpub661MyMwAqRbcFa4vkmBVnqhGEgWhewDgAv3xmFRny74D3sGHz6KTTbcskg2vZEMbEwxc4oaR435oczhSu4GdNwhwiVewcewU8A1Rr6HehAU'; - -var child1_priv = 'xprv9v414VeuxMoGt3t7jzkPni79suCfkgFwjxG38X2wgfg2mrYtV4Bhj3prhDDCcBiJrz9n4xLYoDtBFRuQmreVLKzmiZAqvbGpx5q4yHfzfah'; -var child1_pub = 'xpub693MU1BonjMa6Xxar2HQ9r3tRw3AA8yo7BBdvuSZF1D1eet32bVxGr9LYViWMtaLfQaa2StXeUmDG5VELFkU9pc3yfTzCk61WQJdR6ezj7a'; - -var child2_pub = 'xpub693MU1BonjMa8MMoz9opJhrFejcXcGmhMP9gzySLsip4Dz1UrSLT4i2pAimHDyM2onW2H2L2HkbwrZqoizQLMoErXu8mPYxDf8tJUBAfBuT'; - -var child3_priv = 'xprv9v414VeuxMoGusHLt8GowZuX6hn3Cp3qzAE6Cb2jKPH5MBgLJu2CWuiLKTdWV8WoNFYvpCcBfbpWfeyEQ8zytZW5qy39roTcugBGUkeAvCc'; -var child3_pub = 'xpub693MU1BonjMa8MMoz9opJhrFejcXcGmhMP9gzySLsip4Dz1UrSLT4i2pAimHDyM2onW2H2L2HkbwrZqoizQLMoErXu8mPYxDf8tJUBAfBuT'; - -var child4_priv = 'xprv9v414VeuxMoGyViVYuzEN5vLDzff3nkrH5Bf4KzD1iTeY855Q4cCc6xPPNoc6MJcsqqRQiGqR977cEEGK2mhVp7ALKHqY1icEw3Q9UmfQ1v'; -var child4_pub = 'xpub693MU1BonjMaBynxewXEjDs4n2W9TFUheJ7FriPpa3zdQvQDwbvT9uGsEebvioAcYbtRUU7ge4yVgj8WDLrwtwuXKTWiieFoYX2B1JYUEfK'; - -var child5_priv = 'xprv9xaK29Nm86ytEwsV9YSsL3jWYR6KtZYY3cKdjAbxHrwKyxH9YWoxvqKwtgQmExGpxAEDrwB4WK9YG1iukth3XiSgsxXLK1W3NB31gLee8fi'; -var child5_pub = 'xpub6BZfReuexUYBTRwxFZyshBgF6SvpJ2GPQqFEXZ1ZrCUJrkcJ648DUdeRjx9QiNQxQvPcHYV3rGkvuExFQbVRS4kU5ynx4fAsWWhHgyPh1pP'; - -var child6_priv = 'xprv9xaK29Nm86ytGx9uDhNKUBjvbJ1sAEM11aYxGQS66Rmg6oHwy7HbB6kWwMHvukzdbPpGhfNXhZgePWFHm1DCh5PACPFywJJKr1AnUJTLjUc'; -var child6_pub = 'xpub6BZfReuexUYBVSENKiuKqKgf9KrMZh4rNoUZ4nqhemJeybd6Webqiu4zndBwa9UB4Jvr5jB5Bcgng6reXAKCuDiVm7zhzJ13BUDBiM8HidZ'; - -var master = hd.priv({ seed: seed }); -var child1 = master.derive(0); -var child2 = master.hdpub.derive(1); -var child3 = master.derive(1); -var child4 = master.derive(2); -var child5 = child4.derive(0); -var child6 = child4.derive(1); -master._unbuild(master.xprivkey); -master.hdpub._unbuild(master.hdpub.xpubkey); - -// console.log(new HDSeed({ -// // I have the same combination on my luggage: -// entropy: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], -// passphrase: 'foo' -// })); - -try { - assert.equal(master.xprivkey, master_priv); - assert.equal(master.xpubkey, master_pub); - - assert.equal(child1.xprivkey, child1_priv); - assert.equal(child1.xpubkey, child1_pub); - - assert.equal(child2.xpubkey, child2_pub); - - assert.equal(child3.xprivkey, child3_priv); - assert.equal(child3.xpubkey, child3_pub); - - assert.equal(child4.xprivkey, child4_priv); - assert.equal(child4.xpubkey, child4_pub); - - assert.equal(child5.xprivkey, child5_priv); - assert.equal(child5.xpubkey, child5_pub); - - assert.equal(child6.xprivkey, child6_priv); - assert.equal(child6.xpubkey, child6_pub); -} catch (e) { - console.log('master xprivkey: %s', master.xprivkey); - console.log('master xpubkey: %s', master.xpubkey); - console.log('-'); - console.log('child1 xprivkey: %s', child1.xprivkey); - console.log('child1 xpubkey: %s', child1.xpubkey); - console.log('-'); - console.log('child2 xpubkey: %s', child2.xpubkey); - console.log('-'); - console.log('child3 xprivkey: %s', child3.xprivkey); - console.log('child3 xpubkey: %s', child3.xpubkey); - console.log('-'); - console.log('child3 xprivkey: %s', child4.xprivkey); - console.log('child3 xpubkey: %s', child4.xpubkey); - console.log('-'); - console.log('child5 xprivkey: %s', child5.xprivkey); - console.log('child5 xpubkey: %s', child5.xpubkey); - console.log('-'); - console.log('child6 xprivkey: %s', child6.xprivkey); - console.log('child6 xpubkey: %s', child6.xpubkey); - throw e; -} +hd.pbkdf2 = pbkdf2; diff --git a/test/hd-test.js b/test/hd-test.js new file mode 100644 index 00000000..ac7d8534 --- /dev/null +++ b/test/hd-test.js @@ -0,0 +1,95 @@ +// var assert = require('assert'); +var bn = require('bn.js'); +var bcoin = require('../'); +var utils = bcoin.utils; +var assert = utils.assert; + +describe('HD', function() { + var phrase = 'volume doll flush federal inflict tomato result property total curtain shield aisle'; + + var seed = '5559092716434b83f158bffb51337a944529ae30d7e62d46d3be0c66fa4b36e8d60ccfd2c976b831885dc9df9ac3716ee4bf90003f25621070a49cbea58f528b'; + + var master_priv = 'xprv9s21ZrQH143K35zTejeVRhkXgegDFUVpoh8Mxs2BQmXEB4w9SZ1CuoJPuQ2KGQrS1ZF3Pk7V7KWHn7FqR2JbAE9Bh8PURnrFnrmArj4kxos'; + var master_pub = 'xpub661MyMwAqRbcFa4vkmBVnqhGEgWhewDgAv3xmFRny74D3sGHz6KTTbcskg2vZEMbEwxc4oaR435oczhSu4GdNwhwiVewcewU8A1Rr6HehAU'; + + var child1_priv = 'xprv9v414VeuxMoGt3t7jzkPni79suCfkgFwjxG38X2wgfg2mrYtV4Bhj3prhDDCcBiJrz9n4xLYoDtBFRuQmreVLKzmiZAqvbGpx5q4yHfzfah'; + var child1_pub = 'xpub693MU1BonjMa6Xxar2HQ9r3tRw3AA8yo7BBdvuSZF1D1eet32bVxGr9LYViWMtaLfQaa2StXeUmDG5VELFkU9pc3yfTzCk61WQJdR6ezj7a'; + + var child2_pub = 'xpub693MU1BonjMa8MMoz9opJhrFejcXcGmhMP9gzySLsip4Dz1UrSLT4i2pAimHDyM2onW2H2L2HkbwrZqoizQLMoErXu8mPYxDf8tJUBAfBuT'; + + var child3_priv = 'xprv9v414VeuxMoGusHLt8GowZuX6hn3Cp3qzAE6Cb2jKPH5MBgLJu2CWuiLKTdWV8WoNFYvpCcBfbpWfeyEQ8zytZW5qy39roTcugBGUkeAvCc'; + var child3_pub = 'xpub693MU1BonjMa8MMoz9opJhrFejcXcGmhMP9gzySLsip4Dz1UrSLT4i2pAimHDyM2onW2H2L2HkbwrZqoizQLMoErXu8mPYxDf8tJUBAfBuT'; + + var child4_priv = 'xprv9v414VeuxMoGyViVYuzEN5vLDzff3nkrH5Bf4KzD1iTeY855Q4cCc6xPPNoc6MJcsqqRQiGqR977cEEGK2mhVp7ALKHqY1icEw3Q9UmfQ1v'; + var child4_pub = 'xpub693MU1BonjMaBynxewXEjDs4n2W9TFUheJ7FriPpa3zdQvQDwbvT9uGsEebvioAcYbtRUU7ge4yVgj8WDLrwtwuXKTWiieFoYX2B1JYUEfK'; + + var child5_priv = 'xprv9xaK29Nm86ytEwsV9YSsL3jWYR6KtZYY3cKdjAbxHrwKyxH9YWoxvqKwtgQmExGpxAEDrwB4WK9YG1iukth3XiSgsxXLK1W3NB31gLee8fi'; + var child5_pub = 'xpub6BZfReuexUYBTRwxFZyshBgF6SvpJ2GPQqFEXZ1ZrCUJrkcJ648DUdeRjx9QiNQxQvPcHYV3rGkvuExFQbVRS4kU5ynx4fAsWWhHgyPh1pP'; + + var child6_priv = 'xprv9xaK29Nm86ytGx9uDhNKUBjvbJ1sAEM11aYxGQS66Rmg6oHwy7HbB6kWwMHvukzdbPpGhfNXhZgePWFHm1DCh5PACPFywJJKr1AnUJTLjUc'; + var child6_pub = 'xpub6BZfReuexUYBVSENKiuKqKgf9KrMZh4rNoUZ4nqhemJeybd6Webqiu4zndBwa9UB4Jvr5jB5Bcgng6reXAKCuDiVm7zhzJ13BUDBiM8HidZ'; + + var master, child1, child2, child3, child4, child5, child6; + + it('should create a pbkdf2 seed', function() { + var checkSeed = utils.toHex(bcoin.hd.pbkdf2(phrase, 'mnemonic' + 'foo', 2048, 64)); + assert.equal(checkSeed, seed); + }); + + it('should create master private key', function() { + master = bcoin.hd.priv({ seed: seed }); + assert.equal(master.xprivkey, master_priv); + assert.equal(master.xpubkey, master_pub); + }); + + it('should derive(0) child from master', function() { + child1 = master.derive(0); + assert.equal(child1.xprivkey, child1_priv); + assert.equal(child1.xpubkey, child1_pub); + }); + + it('should derive(1) child from master public key', function() { + child2 = master.hdpub.derive(1); + assert.equal(child2.xpubkey, child2_pub); + }); + + it('should derive(1) child from master', function() { + child3 = master.derive(1); + assert.equal(child3.xprivkey, child3_priv); + assert.equal(child3.xpubkey, child3_pub); + }); + + it('should derive(2) child from master', function() { + child4 = master.derive(2); + assert.equal(child4.xprivkey, child4_priv); + assert.equal(child4.xpubkey, child4_pub); + }); + + it('should derive(0) child from child(2)', function() { + child5 = child4.derive(0); + assert.equal(child5.xprivkey, child5_priv); + assert.equal(child5.xpubkey, child5_pub); + }); + + it('should derive(1) child from child(2)', function() { + child6 = child4.derive(1); + assert.equal(child6.xprivkey, child6_priv); + assert.equal(child6.xpubkey, child6_pub); + }); + + it('should deserialize master private key', function() { + master._unbuild(master.xprivkey); + }); + + it('should deserialize master public key', function() { + master.hdpub._unbuild(master.hdpub.xpubkey); + }); + + it('should create an hd seed', function() { + var seed = new bcoin.hd.seed({ + // I have the same combination on my luggage: + entropy: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + passphrase: 'foo' + }); + }); +}); From 77623bb8d6352805a7d1fdfbdcfccfb8dd8c1ed9 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 7 Dec 2015 16:34:44 -0800 Subject: [PATCH 33/59] remove useless code. --- lib/bcoin/tx.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index bab63608..70ffe2c9 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -439,11 +439,6 @@ TX.prototype.maxSize = function maxSize() { redeem = script[script.length - 1]; m = redeem[0]; n = redeem[redeem.length - 2]; - // If using pushdata instead of OP_1-16: - if (Array.isArray(m)) - m = m[0]; - if (Array.isArray(n)) - n = n[0]; } else { // May end up in a higher fee if we // do not have the redeem script available. From 7016991366d3b6f9de7039ce1987b73033efcec6 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 7 Dec 2015 18:43:52 -0800 Subject: [PATCH 34/59] order keys and signatures for multisig correctly. --- lib/bcoin/script.js | 5 +++ lib/bcoin/tx.js | 88 +++++++++++++++++++++++++++++++++++---------- lib/bcoin/utils.js | 2 ++ lib/bcoin/wallet.js | 22 +++++++----- test/wallet-test.js | 2 ++ 5 files changed, 93 insertions(+), 26 deletions(-) diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 8db84dd4..615d1e96 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -1,4 +1,5 @@ var bcoin = require('../bcoin'); +var bn = require('bn.js'); var constants = bcoin.protocol.constants; var utils = bcoin.utils; var assert = bcoin.utils.assert; @@ -265,6 +266,10 @@ script.multisig = function(keys, m, n) { // [ [ n ], 'checkmultisig' ] // ); + keys = keys.sort(function(a, b) { + return new bn(a).cmp(new bn(b)) > 0; + }); + // Using OP_1-16 for m and n: return [ m ].concat( keys, diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 70ffe2c9..d677b5c2 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -144,25 +144,32 @@ TX.prototype.signature = function(input, key, type) { }; // Build the scriptSigs for inputs, excluding the signatures -TX.prototype.scriptInput = function(input, pub, nsigs) { +TX.prototype.scriptInput = function(input, pub) { // Get the previous output's subscript var s = input.out.tx.getSubscript(input.out.index); + // Already has a script template (at least) + if (input.script.length) + return; + // P2PKH and simple tx if (bcoin.script.isPubkeyhash(s) || bcoin.script.isSimplePubkeyhash(s)) { input.script = [ [], pub ]; return; } + // NOTE for multisig: Technically we should create m signature slots, + // but we create n signature slots so we can order the signatures properly. + // Multisig // raw format: OP_FALSE [sig-1] [sig-2] ... if (bcoin.script.isMultisig(s)) { input.script = [ [] ]; - var m = s[0]; + var n = s[s.length - 2]; // If using pushdata instead of OP_1-16: - if (Array.isArray(m)) - m = m[0]; - for (var i = 0; i < m; i++) + if (Array.isArray(n)) + n = n[0]; + for (var i = 0; i < n; i++) input.script[i + 1] = []; return; } @@ -171,8 +178,8 @@ TX.prototype.scriptInput = function(input, pub, nsigs) { // p2sh format: OP_FALSE [sig-1] [sig-2] ... [redeem-script] if (bcoin.script.isScripthash(s)) { input.script = [ [] ]; - var m = pub[0] - constants.opcodes['1'] + 1; - for (var i = 0; i < m; i++) + var n = pub[pub.length - 2] - constants.opcodes['1'] + 1; + for (var i = 0; i < n; i++) input.script[i + 1] = []; // P2SH requires the redeem script after signatures input.script.push(pub); @@ -213,20 +220,65 @@ TX.prototype.signInput = function(input, key, type) { // p2sh format: OP_FALSE [sig-1] [sig-2] ... [redeem-script] if (bcoin.script.isMultisig(s) || bcoin.script.isScripthash(s)) { var len = input.script.length; + var redeem; - if (bcoin.script.isScripthash(s)) + if (bcoin.script.isScripthash(s)) { len--; + redeem = bcoin.script.decode(input.script[input.script.length - 1]); + } else { + redeem = s; + } + var m = redeem[0]; + var n = redeem[s.length - 2]; + // If using pushdata instead of OP_1-16: + if (Array.isArray(m)) + m = m[0]; + if (Array.isArray(n)) + n = n[0]; + + var keys = redeem.slice(1, -2); + var pub = key.getPublic(true, 'array'); + var pubn = key.getPublic(false, 'array'); + + // Find the key index so we can place + // the signature in the same index. + for (var ki = 0; ki < keys.length; ki++) { + if (utils.isEqual(pub, keys[ki]) || utils.isEqual(pubn, keys[ki])) + break; + } + + if (ki === keys.length) + throw new Error('Public key is not in the prev_out script'); + + if (ki + 1 > len - 1) + throw new Error('No signature slot available'); + + // Add our signature to the correct slot + // and count the total number of signatures. + var totalSigs = 0; for (var i = 1; i < len; i++) { - // Already signed - if (utils.isEqual(input.script[i], signature)) - break; + if (input.script[i].length) { + totalSigs++; + continue; + } - if (input.script[i].length === 0) { + if (i - 1 === ki) { + if (totalSigs >= m) + continue; input.script[i] = signature; - break; + totalSigs++; } } + + // All signatures added. Finalize by removing empty slots. + if (totalSigs >= m) { + for (var i = len - 1; i >= 1; i--) { + if (!input.script[i].length) + input.script.splice(i, 1); + } + } + return; } @@ -234,9 +286,9 @@ TX.prototype.signInput = function(input, key, type) { }; // Build the scriptSig and sign it -TX.prototype.scriptSig = function(input, key, pub, type, nsigs) { +TX.prototype.scriptSig = function(input, key, pub, type) { // Build script for input - this.scriptInput(input, pub, nsigs); + this.scriptInput(input, pub); // Sign input this.signInput(input, key, type); @@ -403,7 +455,7 @@ TX.prototype.maxSize = function maxSize() { var size = copy.render().length; // Add size for signatures and public keys - copy.inputs.forEach(function(input) { + copy.inputs.forEach(function(input, i) { // Get the previous output's script // var s = input.out.tx.outputs[input.out.index].script; @@ -435,7 +487,7 @@ TX.prototype.maxSize = function maxSize() { if (bcoin.script.isScripthash(s)) { var script = this.inputs[i].script; var redeem, m, n; - if (script) { + if (script.length) { redeem = script[script.length - 1]; m = redeem[0]; n = redeem[redeem.length - 2]; @@ -475,7 +527,7 @@ TX.prototype.maxSize = function maxSize() { // 2. Add inputs with utxos and change output: // - this.fillUnspent(unspentItems, [changeAddr]); // 3. Fill input scripts (for each input): -// - this.scriptInput(input, pub, [nsigs]) +// - this.scriptInput(input, pub) // - this.signInput(input, key, [sigHashType]) TX.prototype.utxos = function utxos(unspent) { // NOTE: tx should be prefilled with all outputs diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index faa13036..f96ded1c 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -417,6 +417,8 @@ RequestCache.prototype.fullfill = function fullfill(id, err, data) { utils.asyncify = function asyncify(fn) { return function _asynicifedFn(err, data1, data2) { + if (!fn) + return; utils.nextTick(function() { fn(err, data1, data2); }); diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index afe2d1e4..bc12be50 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -40,6 +40,8 @@ function Wallet(options, passphrase) { } else if (options.hd) { this.hd = bcoin.hd.priv(options); this.key = this.hd.pair; + } else if (options.key) { + this.key = options.key; } else if (options.passphrase) { this.key = bcoin.ecdsa.genKeyPair({ pers: options.scope, @@ -74,7 +76,7 @@ function Wallet(options, passphrase) { throw new Error('n ranges between 1 and 7'); } - if (this.sharedKeys.length !== this.m - 1) { + if (this.sharedKeys.length < this.m - 1) { throw new Error(this.m + ' public keys required'); } @@ -153,7 +155,7 @@ Wallet.prototype.getPublicKey = function getPublicKey(enc) { if (this.addressType === 'p2sh') { var keys = this.getPublicKeys(); - pub = bcoin.script.encode(bcoin.script.multisig(keys, m, n)); + pub = bcoin.script.encode(bcoin.script.multisig(keys, this.m, this.n)); } if (enc === 'base58') @@ -166,10 +168,15 @@ Wallet.prototype.getPublicKey = function getPublicKey(enc) { Wallet.prototype.getPublicKeys = function() { var keys = this.sharedKeys.slice().map(utils.toKeyArray); - if (keys.length < this.m) { - var pub = this.key.getPublic(this.compressed, 'array'); - keys.push(pub); - } + + // if (keys.length < this.m) { + var pub = this.key.getPublic(this.compressed, 'array'); + keys.push(pub); + + keys = keys.sort(function(a, b) { + return new bn(a).cmp(new bn(b)) > 0; + }); + return keys; }; @@ -286,7 +293,6 @@ Wallet.prototype.sign = function sign(tx, type, inputs) { var pub = this.getPublicKey(); var key = this.key; - var nsigs = this.m; inputs = inputs || tx.inputs; @@ -296,7 +302,7 @@ Wallet.prototype.sign = function sign(tx, type, inputs) { if (!input.out.tx || !this.ownOutput(input.out.tx)) return false; - tx.scriptSig(input, key, pub, type, nsigs); + tx.scriptSig(input, key, pub, type); return true; }, this); diff --git a/test/wallet-test.js b/test/wallet-test.js index bf48ce63..61d4a411 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -46,6 +46,7 @@ describe('Wallet', function() { it('should multisign/verify TX', function() { var w = bcoin.wallet(); + var k2 = bcoin.wallet().getPublicKey(); // Input transcation var src = bcoin.tx({ @@ -53,6 +54,7 @@ describe('Wallet', function() { value: 5460 * 2, minSignatures: 1, keys: [ w.getPublicKey(), w.getPublicKey().concat(1) ] + // keys: [ w.getPublicKey(), k2 ] }, { value: 5460 * 2, address: w.getAddress() + 'x' From a770e2bd7b7cd2b03883aa03de72ab7aeda6a600 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 7 Dec 2015 19:18:52 -0800 Subject: [PATCH 35/59] add p2sh multisig test. --- test/wallet-test.js | 73 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/test/wallet-test.js b/test/wallet-test.js index 61d4a411..c1069a8d 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -225,4 +225,77 @@ describe('Wallet', function() { cb(); }); + + it('should verify 2-of-3 p2sh tx', function(cb) { + // Generate 3 key pairs + var key1 = bcoin.ecdsa.genKeyPair(); + var key2 = bcoin.ecdsa.genKeyPair(); + var key3 = bcoin.ecdsa.genKeyPair(); + + // Grab the 3 pubkeys + var pub1 = key1.getPublic(true, 'array'); + var pub2 = key2.getPublic(true, 'array'); + var pub3 = key3.getPublic(true, 'array'); + + // Create 3 2-of-3 wallets with our pubkeys as "shared keys" + var w1 = bcoin.wallet({ + key: key1, + addressType: 'p2sh', + sharedKeys: [pub2, pub3], + m: 2, + n: 3 + }); + var w2 = bcoin.wallet({ + key: key2, + addressType: 'p2sh', + sharedKeys: [pub1, pub3], + m: 2, + n: 3 + }); + var w3 = bcoin.wallet({ + key: key3, + addressType: 'p2sh', + sharedKeys: [pub1, pub2], + m: 2, + n: 3 + // multisig: { + // type: 'p2sh', + // keys: [pub1, pub2], + // m: 2, + // n: 3 + // } + }); + var receive = bcoin.wallet(); + + // Our p2sh address + var addr = w1.getAddress(); + assert.equal(w1.getAddress(), addr); + assert.equal(w2.getAddress(), addr); + assert.equal(w3.getAddress(), addr); + + // Add a shared unspent transaction to our wallets + var utx = bcoin.tx(); + utx.output({ address: addr, value: 5460 * 10 }); + + w1.addTX(utx); + w2.addTX(utx); + w3.addTX(utx); + + // Create a tx requiring 2 signatures + var send = bcoin.tx(); + send.output({ address: receive.getAddress(), value: 5460 }); + var result = w1.fill(send); + assert(result); + w2.sign(send); + + // XXX Still verifies for some reason. + send.inputs[0].script[1] = []; + // send.inputs[0].script[2] = []; + assert(send.verify()); + + console.log(utx.outputs[0].script); + console.log(send.inputs[0].script); + + cb(); + }); }); From adbb0099a1f40e17405a509e2598a50e152a9a24 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 8 Dec 2015 11:24:22 -0800 Subject: [PATCH 36/59] add checklocktimeverify. pass in tx and input to execute() instead of using hasher callback. --- lib/bcoin/protocol/constants.js | 5 +++- lib/bcoin/script.js | 49 +++++++++++++++++++++++++++------ lib/bcoin/tx.js | 7 ++--- 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/lib/bcoin/protocol/constants.js b/lib/bcoin/protocol/constants.js index f52804e7..e22dcb83 100644 --- a/lib/bcoin/protocol/constants.js +++ b/lib/bcoin/protocol/constants.js @@ -137,7 +137,8 @@ exports.opcodes = { checksig: 0xac, checksigverify: 0xad, checkmultisig: 0xae, - checkmultisigverify: 0xaf + checkmultisigverify: 0xaf, + checklocktimeverify: 0xb1 }; for (var i = 1; i <= 16; i++) @@ -169,3 +170,5 @@ exports.block = { maxSigops: 1000000 / 50, maxOrphanTx: 1000000 / 100 }; + +exports.locktimeThreshold = 500000000; // Tue Nov 5 00:53:20 1985 UTC diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 615d1e96..0adc2478 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -130,10 +130,12 @@ script.verify = function verify(hash, sig, pub) { } }; -script.execute = function execute(s, stack, hasher) { +script.execute = function execute(s, stack, tx, index) { if (s.length > 10000) return false; + var input = tx.inputs[index]; + for (var pc = 0; pc < s.length; pc++) { var o = s[pc]; if (Array.isArray(o)) { @@ -145,6 +147,8 @@ script.execute = function execute(s, stack, hasher) { return false; stack.push(stack[stack.length - 1]); + } else if (o === 'drop') { + stack.pop(); } else if (o === 'hash160') { if (stack.length === 0) return false; @@ -163,7 +167,7 @@ script.execute = function execute(s, stack, hasher) { } } else if (o === 'checksigverify' || o === 'checksig') { - if (!hasher || stack.length < 2) + if (!tx || stack.length < 2) return false; var pub = stack.pop(); @@ -172,9 +176,8 @@ script.execute = function execute(s, stack, hasher) { if (!constants.rhashType[type & 0x7f]) return false; - var hash = hasher; - if (typeof hash === 'function') - hash = hash(type); + var subscript = input.out.tx.getSubscript(input.out.index); + var hash = tx.subscriptHash(index, subscript, type); var res = script.verify(hash, sig.slice(0, -1), pub); if (o === 'checksigverify') { @@ -184,7 +187,7 @@ script.execute = function execute(s, stack, hasher) { stack.push(res ? [ 1 ] : []); } } else if (o === 'checkmultisigverify' || o === 'checkmultisig') { - if (!hasher || stack.length < 3) + if (!tx || stack.length < 3) return false; var n = stack.pop(); @@ -220,9 +223,8 @@ script.execute = function execute(s, stack, hasher) { if (!constants.rhashType[type & 0x7f]) return false; - var hash = hasher; - if (typeof hash === 'function') - hash = hash(type); + var subscript = input.out.tx.getSubscript(input.out.index); + var hash = tx.subscriptHash(index, subscript, type); var res = false; for (; !res && j < n; j++) @@ -241,6 +243,35 @@ script.execute = function execute(s, stack, hasher) { } else { stack.push(res ? [ 1 ] : []); } + } else if (o === 'checklocktimeverify') { + // input: [[], sig1, sig2, 1] + // prev_out: [[lock], 'checklocktimeverify', 'drop', + // 'dup', 'hash160', pubkey, 'equalverify', 'checksig'] + if (stack.length === 0) + return false; + + var lock = stack[stack.length - 1]; + if (lock.length !== 1) + return false; + + lock = lock[0]; + + if (lock < 0) + return false; + + var threshold = constants.locktimeThreshold; + if (!( + (tx.lock < threshold && lock < threshold) || + (tx.lock >= threshold && lock >= threshold) + )) { + return false; + } + + if (lock > tx.lock) + return false; + + if (input.seq === 0xffffffff) + return false; } else { // Unknown operation return false; diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index d677b5c2..5b7fbc5c 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -427,13 +427,10 @@ TX.prototype.verify = function verify(index, force) { assert(input.out.tx.outputs.length > input.out.index); - var subscript = input.out.tx.getSubscript(input.out.index); - var hasher = this.subscriptHash.bind(this, i, subscript); - var stack = []; - bcoin.script.execute(input.script, stack); + bcoin.script.execute(input.script, stack, this, i); var prev = input.out.tx.outputs[input.out.index].script; - var res = bcoin.script.execute(prev, stack, hasher); + var res = bcoin.script.execute(prev, stack, this, i); if (!res) return false; From 3c0bf360e22eed4e922e8afcce632daeada959d5 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 8 Dec 2015 11:35:42 -0800 Subject: [PATCH 37/59] add bip66 strict der sig checking. --- lib/bcoin/script.js | 101 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 0adc2478..35a8a3c9 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -176,6 +176,9 @@ script.execute = function execute(s, stack, tx, index) { if (!constants.rhashType[type & 0x7f]) return false; + if (!script.isValidSig(sig)) + return false; + var subscript = input.out.tx.getSubscript(input.out.index); var hash = tx.subscriptHash(index, subscript, type); @@ -226,6 +229,9 @@ script.execute = function execute(s, stack, tx, index) { var subscript = input.out.tx.getSubscript(input.out.index); var hash = tx.subscriptHash(index, subscript, type); + if (!script.isValidSig(sig)) + return false; + var res = false; for (; !res && j < n; j++) res = script.verify(hash, sig.slice(0, -1), keys[j]); @@ -413,3 +419,98 @@ script.isNullData = function isNullData(s) { Array.isArray(s[1]) && s[1].length <= 40; }; + +// https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki +/** + * A canonical signature exists of: <30> <02> <02> + * Where R and S are not negative (their first byte has its highest bit not set), and not + * excessively padded (do not start with a 0 byte, unless an otherwise negative number follows, + * in which case a single 0 byte is necessary and even required). + * + * See https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623 + * + * This function is consensus-critical since BIP66. + */ +script.isValidSig = function(sig) { + // Empty signature. Not strictly DER encoded, but allowed to provide a + // compact way to provide an invalid signature for use with CHECK(MULTI)SIG + if (sig.length === 0) + return true; + + // Format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] [sighash] + // * total-length: 1-byte length descriptor of everything that follows, + // excluding the sighash byte. + // * R-length: 1-byte length descriptor of the R value that follows. + // * R: arbitrary-length big-endian encoded R value. It must use the shortest + // possible encoding for a positive integers (which means no null bytes at + // the start, except a single one when the next byte has its highest bit set). + // * S-length: 1-byte length descriptor of the S value that follows. + // * S: arbitrary-length big-endian encoded S value. The same rules apply. + // * sighash: 1-byte value indicating what data is hashed (not part of the DER + // signature) + + // Minimum and maximum size constraints. + if (sig.length < 9) + return false; + if (sig.length > 73) + return false; + + // A signature is of type 0x30 (compound). + if (sig[0] !== 0x30) + return false; + + // Make sure the length covers the entire signature. + if (sig[1] !== sig.length - 3) + return false; + + // Extract the length of the R element. + var lenR = sig[3]; + + // Make sure the length of the S element is still inside the signature. + if (5 + lenR >= sig.length) + return false; + + // Extract the length of the S element. + var lenS = sig[5 + lenR]; + + // Verify that the length of the signature matches the sum of the length + // of the elements. + if (lenR + lenS + 7 !== sig.length) + return false; + + // Check whether the R element is an integer. + if (sig[2] !== 0x02) + return false; + + // Zero-length integers are not allowed for R. + if (lenR === 0) + return false; + + // Negative numbers are not allowed for R. + if (sig[4] & 0x80) + return false; + + // Null bytes at the start of R are not allowed, unless R would + // otherwise be interpreted as a negative number. + if (lenR > 1 && (sig[4] === 0x00) && !(sig[5] & 0x80)) + return false; + + // Check whether the S element is an integer. + if (sig[lenR + 4] !== 0x02) + return false; + + // Zero-length integers are not allowed for S. + if (lenS === 0) + return false; + + // Negative numbers are not allowed for S. + if (sig[lenR + 6] & 0x80) + return false; + + // Null bytes at the start of S are not allowed, unless S would otherwise be + // interpreted as a negative number. + if (lenS > 1 && (sig[lenR + 6] === 0x00) && !(sig[lenR + 7] & 0x80)) + return false; + + return true; +}; From fd7d20be9cfd7eeb8451837af87496b3469b1e6d Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 8 Dec 2015 11:46:14 -0800 Subject: [PATCH 38/59] enforce strict ordering with checkmultisig. --- lib/bcoin/script.js | 15 +++++++++++---- test/wallet-test.js | 6 +++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 35a8a3c9..dd1eb65f 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -220,7 +220,8 @@ script.execute = function execute(s, stack, tx, index) { // Get signatures var succ = 0; - for (var i = 0, j = 0; i < m && j < n; i++) { + // for (var i = 0, j = 0; i < m && j < n; i++) { + for (var i = 0; i < m; i++) { var sig = stack.pop(); var type = sig[sig.length - 1]; if (!constants.rhashType[type & 0x7f]) @@ -232,9 +233,12 @@ script.execute = function execute(s, stack, tx, index) { if (!script.isValidSig(sig)) return false; - var res = false; - for (; !res && j < n; j++) - res = script.verify(hash, sig.slice(0, -1), keys[j]); + // var res = false; + // for (; !res && j < n; j++) + // res = script.verify(hash, sig.slice(0, -1), keys[j]); + + // Strict order: + var res = script.verify(hash, sig.slice(0, -1), keys.pop()); if (res) succ++; } @@ -284,6 +288,9 @@ script.execute = function execute(s, stack, tx, index) { } } + if (stack.length > 1000) + return false; + return true; }; diff --git a/test/wallet-test.js b/test/wallet-test.js index c1069a8d..41fdf373 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -289,12 +289,12 @@ describe('Wallet', function() { w2.sign(send); // XXX Still verifies for some reason. - send.inputs[0].script[1] = []; + // send.inputs[0].script[1] = []; // send.inputs[0].script[2] = []; assert(send.verify()); - console.log(utx.outputs[0].script); - console.log(send.inputs[0].script); + // console.log(utx.outputs[0].script); + // console.log(send.inputs[0].script); cb(); }); From a9a9cf08794469863e82e87cc9fcb4cba64abcf6 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 8 Dec 2015 13:13:54 -0800 Subject: [PATCH 39/59] add more opcodes to script.execute. handle codesep properly. --- lib/bcoin/protocol/constants.js | 12 +- lib/bcoin/script.js | 301 +++++++++++++++++++++++++++++++- lib/bcoin/utils.js | 8 + 3 files changed, 309 insertions(+), 12 deletions(-) diff --git a/lib/bcoin/protocol/constants.js b/lib/bcoin/protocol/constants.js index e22dcb83..ba25f547 100644 --- a/lib/bcoin/protocol/constants.js +++ b/lib/bcoin/protocol/constants.js @@ -59,7 +59,7 @@ exports.opcodes = { pushdata4: 0x4e, negate1: 0x4f, - nop: 0x61, + nop1: 0x61, if_: 0x63, notif: 0x64, else_: 0x67, @@ -144,8 +144,14 @@ exports.opcodes = { for (var i = 1; i <= 16; i++) exports.opcodes[i] = 0x50 + i; -exports.opcodes['false'] = exports.opcodes['0']; -exports.opcodes['true'] = exports.opcodes['1']; +for (var i = 0; i <= 7; i++) + exports.opcodes['nop' + (i + 3)] = 0xb2 + i; + +// exports.opcodes['false'] = exports.opcodes['0']; +// exports.opcodes['true'] = exports.opcodes['1']; + +// exports.opcodes['if'] = exports.opcodes.if_; +// exports.opcodes['else'] = exports.opcodes.else_; exports.opcodesByVal = new Array(256); Object.keys(exports.opcodes).forEach(function(name) { diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index dd1eb65f..b2501be7 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -135,9 +135,15 @@ script.execute = function execute(s, stack, tx, index) { return false; var input = tx.inputs[index]; + var lastSep = -1; + + stack.alt = stack.alt || []; + + // TODO: if statements for (var pc = 0; pc < s.length; pc++) { var o = s[pc]; + if (Array.isArray(o)) { stack.push(o); } else if (typeof o === 'number' && o >= 1 && o <= 16) { @@ -148,7 +154,287 @@ script.execute = function execute(s, stack, tx, index) { stack.push(stack[stack.length - 1]); } else if (o === 'drop') { + if (stack.length === 0) + return false; + stack.pop(); + } else if (o === 'nop1' || o === 'nop3' || o === 'nop4' + || o === 'nop5' || o === 'nop6' || o === 'nop7' + || o === 'nop8' || o === 'nop9' || o === 'nop10') { + ; + // OP_EVAL + // if (o === 'nop1') { + // var evalScript = script.decode(stack.pop()); + // var res = script.execute(evalScript, stack, tx, index); + // if (!res) + // return false; + // } + } else if (o === 'verify') { + if (stack.length === 0) + return false; + if (new bn(stack[stack.length - 1]).cmp(0) === 0) + return false; + } else if (o === 'return') { + return false; + } else if (o === 'toaltstack') { + if (stack.length === 0) + return false; + stack.alt.push(stack.pop()); + } else if (o === 'fromaltstack') { + if (stack.alt.length === 0) + return false; + stack.push(stack.alt.pop()); + } else if (o === 'ifdup') { + if (stack.length === 0) + return false; + if (new bn(stack[stack.length - 1]).cmp(0) !== 0) + stack.push(new bn(stack[stack.length - 1]).toArray()); + } else if (o === 'depth') { + stack.push(new bn(stack.length).toArray()); + } else if (o === 'nip') { + if (stack.length < 2) + return false; + stack.splice(stack.length - 2, 1); + } else if (o === 'over') { + if (stack.length < 2) + return false; + stack.push(stack[stack.length - 2]); + } else if (o === 'pick' || o === 'roll') { + if (stack.length < 2) + return false; + var n = new bn(stack.pop()).toNumber(); + if (n < 0 || n >= stack.length) + return false; + var v = stack[-n - 1]; + if (o === 'roll') + stack.splice(stack.length - n - 1, 1); + stack.push(v); + } else if (o === 'rot') { + if (stack.length < 3) + return false; + var v3 = stack[stack.length - 3]; + var v2 = stack[stack.length - 2]; + var v1 = stack[stack.length - 1]; + stack[stack.length - 3] = v2; + stack[stack.length - 2] = v3; + + v2 = stack[stack.length - 2]; + stack[stack.length - 2] = v1; + stack[stack.length - 1] = v2; + } else if (o === 'swap') { + if (stack.length < 2) + return false; + var v2 = stack[stack.length - 2]; + var v1 = stack[stack.length - 1]; + stack[stack.length - 2] = v1; + stack[stack.length - 1] = v2; + } else if (o === 'tuck') { + if (stack.length < 2) + return false; + stack.splice(stack.length - 2, 0, stack[stack.length - 1]); + } else if (o === 'drop2') { + if (stack.length < 2) + return false; + stack.pop(); + stack.pop(); + } else if (o === 'dup2') { + if (stack.length < 2) + return false; + var v1 = stack[stack.length - 1]; + var v2 = stack[stack.length - 2]; + stack.push(v1); + stack.push(v2); + } else if (o === 'dup3') { + if (stack.length < 3) + return false; + var v1 = stack[stack.length - 1]; + var v2 = stack[stack.length - 2]; + var v3 = stack[stack.length - 3]; + stack.push(v1); + stack.push(v2); + stack.push(v3); + } else if (o === 'over2') { + if (stack.length < 4) + return false; + var v1 = stack[stack.length - 4]; + var v2 = stack[stack.length - 3]; + stack.push(v1); + stack.push(v2); + } else if (o === 'rot2') { + if (stack.length < 6) + return false; + var v1 = stack[stack.length - 6]; + var v2 = stack[stack.length - 5]; + stack.splice(stack.length - 6, 2); + stack.push(v1); + stack.push(v2); + } else if (o === 'swap2') { + if (stack.length < 4) + return false; + var v4 = stack[stack.length - 4]; + var v3 = stack[stack.length - 3]; + var v2 = stack[stack.length - 2]; + var v1 = stack[stack.length - 1]; + stack[stack.length - 4] = v2; + stack[stack.length - 2] = v4; + stack[stack.length - 3] = v1; + stack[stack.length - 1] = v3; + } else if (o === 'size') { + if (stack.length < 1) + return false; + stack.push(new bn(stack[stack.length - 1].length || 0).toArray()); + } else if (o === 'add1' + || o === 'sub1' + || o === 'negate' + || o === 'abs' + || o === 'not' + || o === 'noteq0') { + if (stack.length < 1) + return false; + var n = new bn(stack.pop()); + switch (o) { + case 'add1': + n.iadd(1); + break; + case 'sub1': + n.isub(1); + break; + case 'negate': + n = n.neg(); + break; + case 'abs': + if (n.cmp(0) < 0) + n = n.neg(); + break; + case 'not': + n = n.cmp(0) === 0; + break; + case 'noteq0': + n = n.cmp(0) !== 0; + break; + default: + return false; + } + stack.push(n.toArray()); + } else if (o === 'add' + || o === 'sub' + || o === 'booland' + || o === 'boolor' + || o === 'numeq' + || o === 'numeqverify' + || o === 'numneq' + || o === 'lt' + || o === 'gt' + || o === 'lte' + || o === 'gte' + || o === 'min' + || o === 'max') { + switch (o) { + case 'add': + case 'sub': + case 'booland': + case 'boolor': + case 'numeq': + case 'numeqverify': + case 'numneq': + case 'lt': + case 'gt': + case 'lte': + case 'gte': + case 'min': + case 'max': + if (stack.length < 2) + return false; + var n2 = new bn(stack.pop()); + var n1 = new bn(stack.pop()); + var n = new bn(0); + switch (o) { + case 'add': + n = n1.add(b2); + break; + case 'sub': + n = n1.sub(n2); + break; + case 'booland': + n = n1.cmp(0) !== 0 && n2.cmp(0) !== 0; + break; + case 'boolor': + n = n1.cmp(0) !== 0 || n2.cmp(0) !== 0; + break; + case 'numeq': + n = n1.cmp(n2) === 0; + break; + case 'numeqverify': + n = n1.cmp(n2) === 0; + break; + case 'numneq': + n = n1.cmp(n2) !== 0; + break; + case 'lt': + n = n1.cmp(n2) < 0; + break; + case 'gt': + n = n1.cmp(n2) > 0; + break; + case 'lte': + n = n1.cmp(n2) <= 0; + break; + case 'gte': + n = n1.cmp(n2) >= 0; + break; + case 'min': + n = n1.cmp(n2) < 0 ? n1 : n2; + break; + case 'max': + n = n1.cmp(n2) > 0 ? n1 : n2; + break; + default: + return false; + } + var res = n.cmp(0) !== 0; + if (o === 'numeqverify') { + if (!res) + return false; + } else { + stack.push(n.toArray()); + // stack.push(res ? [ 1 ] : []); + } + // stack.push(n.toArray()); + // if (op == 'numeqverify') { + // if (n.cmp(0) !== 0) + // stack.pop(); + // else + // return false; + // } + break; + case 'within': + if (stack.length < 3) + return false; + var n3 = new bn(stack.pop()); + var n2 = new bn(stack.pop()); + var n1 = new bn(stack.pop()); + var val = n2.cmp(n1) <= 0 && n1.cmp(n3) < 0; + stack.push(val.cmp(0) !== 0 ? [ 1 ] : []); + break; + } + } else if (o === 'codesep') { + lastSep = pc; + } else if (o === 'ripemd160') { + if (stack.length === 0) + return false; + stack.push(utils.ripemd160(stack.pop())); + } else if (o === 'sha1') { + if (stack.length === 0) + return false; + stack.push(utils.sha1(stack.pop())); + } else if (o === 'sha256') { + if (stack.length === 0) + return false; + stack.push(utils.sha256(stack.pop())); + } else if (o === 'hash256') { + if (stack.length === 0) + return false; + stack.push(utils.dsha256(stack.pop())); } else if (o === 'hash160') { if (stack.length === 0) return false; @@ -165,7 +451,6 @@ script.execute = function execute(s, stack, tx, index) { } else { stack.push(res ? [ 1 ] : []); } - } else if (o === 'checksigverify' || o === 'checksig') { if (!tx || stack.length < 2) return false; @@ -179,7 +464,8 @@ script.execute = function execute(s, stack, tx, index) { if (!script.isValidSig(sig)) return false; - var subscript = input.out.tx.getSubscript(input.out.index); + // var subscript = input.out.tx.getSubscript(input.out.index); + var subscript = s.slice(lastSep + 1); var hash = tx.subscriptHash(index, subscript, type); var res = script.verify(hash, sig.slice(0, -1), pub); @@ -227,7 +513,8 @@ script.execute = function execute(s, stack, tx, index) { if (!constants.rhashType[type & 0x7f]) return false; - var subscript = input.out.tx.getSubscript(input.out.index); + // var subscript = input.out.tx.getSubscript(input.out.index); + var subscript = s.slice(lastSep + 1); var hash = tx.subscriptHash(index, subscript, type); if (!script.isValidSig(sig)) @@ -260,11 +547,7 @@ script.execute = function execute(s, stack, tx, index) { if (stack.length === 0) return false; - var lock = stack[stack.length - 1]; - if (lock.length !== 1) - return false; - - lock = lock[0]; + var lock = new bn(stack[stack.length - 1]).toNumber(); if (lock < 0) return false; @@ -288,7 +571,7 @@ script.execute = function execute(s, stack, tx, index) { } } - if (stack.length > 1000) + if (stack.length + stack.alt.length > 1000) return false; return true; diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index f96ded1c..d4566c6e 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -118,6 +118,14 @@ utils.fromBase58 = function fromBase58(str) { return z.concat(res.toArray()); }; +utils.ripemd160 = function ripemd160(data, enc) { + return hash.ripemd160().update(data, enc).digest(); +}; + +utils.sha1 = function sha1(data, enc) { + return hash.sha1().update(data, enc).digest(); +}; + utils.ripesha = function ripesha(data, enc) { return hash.ripemd160().update(utils.sha256(data, enc)).digest(); }; From 5f8ad78e4dc20de915d94075e630a5a8461fd55e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 8 Dec 2015 14:53:26 -0800 Subject: [PATCH 40/59] have tx.verify handle p2sh correctly. --- lib/bcoin/tx.js | 18 +++++++++++++++++- test/wallet-test.js | 39 ++++++++++++++++++++++++++++++++++----- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 5b7fbc5c..b6c4743f 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -418,6 +418,9 @@ TX.prototype.verify = function verify(index, force) { if (!force && this.ts !== 0) return true; + if (this.inputs.length === 0) + return false; + return this.inputs.every(function(input, i) { if (index !== undefined && index !== i) return true; @@ -434,7 +437,20 @@ TX.prototype.verify = function verify(index, force) { if (!res) return false; - return stack.length > 0 && utils.isEqual(stack.pop(), [ 1 ]); + if (stack.length === 0 || !utils.isEqual(stack.pop(), [ 1 ])) + return false; + + if (bcoin.script.isScripthash(prev)) { + var redeem = input.script[input.script.length - 1]; + if (!Array.isArray(redeem)) + return false; + redeem = bcoin.script.decode(redeem); + res = bcoin.script.execute(redeem, stack, this, i); + if (!res) + return false; + } + + return true; }, this); }; diff --git a/test/wallet-test.js b/test/wallet-test.js index 41fdf373..b47100f0 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -2,6 +2,33 @@ var assert = require('assert'); var bn = require('bn.js'); var bcoin = require('../'); +function printScript(input) { + var scripts = []; + var script = input.script; + scripts.push(script); + var prev = input.out.tx.outputs[input.out.index].script; + scripts.push(prev); + if (bcoin.script.isScripthash(prev)) { + var redeem = bcoin.script.decode(input.script[input.script.length - 1]); + scripts.push(redeem); + } + scripts = scripts.map(function(script) { + return script.map(function(chunk) { + if (Array.isArray(chunk)) { + if (chunk.length === 0) + return [0]; + return [bcoin.utils.toHex(chunk)]; + } + if (typeof chunk === 'number') + return [chunk]; + return chunk; + }); + }); + scripts.forEach(function(script) { + console.log(script); + }); +} + describe('Wallet', function() { it('should generate new key and address', function() { var w = bcoin.wallet(); @@ -284,17 +311,19 @@ describe('Wallet', function() { // Create a tx requiring 2 signatures var send = bcoin.tx(); send.output({ address: receive.getAddress(), value: 5460 }); + assert(!send.verify()); var result = w1.fill(send); assert(result); + + // printScript(send.inputs[0]); + + assert(!send.verify()); w2.sign(send); - // XXX Still verifies for some reason. - // send.inputs[0].script[1] = []; - // send.inputs[0].script[2] = []; assert(send.verify()); - // console.log(utx.outputs[0].script); - // console.log(send.inputs[0].script); + send.inputs[0].script[2] = []; + assert(!send.verify()); cb(); }); From e4cfa878f18ea0805c86ef7305efe8cc845dd55a Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 8 Dec 2015 15:00:34 -0800 Subject: [PATCH 41/59] multisig options. --- lib/bcoin/wallet.js | 62 +++++++++++++++++++++++++++++---------------- test/wallet-test.js | 36 +++++++++++++------------- 2 files changed, 58 insertions(+), 40 deletions(-) diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index bc12be50..8c4b9748 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -56,29 +56,12 @@ function Wallet(options, passphrase) { this.key = bcoin.ecdsa.genKeyPair(); } - this.addressType = options.addressType || 'normal'; + this.addressType = 'normal'; + this.sharedKeys = []; + this.m = 1; + this.n = 1; - // Multisig - this.sharedKeys = (options.sharedKeys || []).map(utils.toKeyArray); - this.m = options.m || 1; - this.n = options.n || 1; - - // Use p2sh multisig by default - if (!options.addressType && this.sharedKeys.length) { - this.addressType = 'p2sh'; - } - - if (this.m < 1 || this.m > this.n) { - throw new Error('m ranges between 1 and n'); - } - - if (this.n < 1 || this.n > 7) { - throw new Error('n ranges between 1 and 7'); - } - - if (this.sharedKeys.length < this.m - 1) { - throw new Error(this.m + ' public keys required'); - } + this.multisig(options.multisig || {}); this.prefix = 'bt/' + this.getAddress() + '/'; this.tx = new bcoin.txPool(this); @@ -124,6 +107,41 @@ Wallet.prototype._init = function init() { }); }; +Wallet.prototype.multisig = function(options) { + var pub = this.key.getPublic(this.compressed, 'array'); + + options.type = options.type || options.addressType; + options.keys = options.keys || options.sharedKeys; + + this.addressType = options.type || 'normal'; + + // Multisig + this.sharedKeys = (options.keys || []).map(utils.toKeyArray); + this.m = options.m || 1; + this.n = options.n || 1; + + this.sharedKeys = this.sharedKeys.filter(function(key) { + return !utils.isEqual(key, pub); + }); + + // Use p2sh multisig by default + if (!options.addressType && this.sharedKeys.length) { + this.addressType = 'p2sh'; + } + + if (this.m < 1 || this.m > this.n) { + throw new Error('m ranges between 1 and n'); + } + + if (this.n < 1 || this.n > 7) { + throw new Error('n ranges between 1 and 7'); + } + + if (this.sharedKeys.length < this.m - 1) { + throw new Error(this.m + ' public keys required'); + } +}; + Wallet.prototype.getPrivateKey = function getPrivateKey(enc) { var priv = this.key.getPrivate(); if (priv) diff --git a/test/wallet-test.js b/test/wallet-test.js index b47100f0..bfbbf100 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -267,30 +267,30 @@ describe('Wallet', function() { // Create 3 2-of-3 wallets with our pubkeys as "shared keys" var w1 = bcoin.wallet({ key: key1, - addressType: 'p2sh', - sharedKeys: [pub2, pub3], - m: 2, - n: 3 + multisig: { + type: 'p2sh', + keys: [pub2, pub3], + m: 2, + n: 3 + } }); var w2 = bcoin.wallet({ key: key2, - addressType: 'p2sh', - sharedKeys: [pub1, pub3], - m: 2, - n: 3 + multisig: { + type: 'p2sh', + keys: [pub1, pub3], + m: 2, + n: 3 + } }); var w3 = bcoin.wallet({ key: key3, - addressType: 'p2sh', - sharedKeys: [pub1, pub2], - m: 2, - n: 3 - // multisig: { - // type: 'p2sh', - // keys: [pub1, pub2], - // m: 2, - // n: 3 - // } + multisig: { + type: 'p2sh', + keys: [pub1, pub2], + m: 2, + n: 3 + } }); var receive = bcoin.wallet(); From 22e092d6b8c6171215c26caa92467f383e3c5f7f Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 8 Dec 2015 16:36:47 -0800 Subject: [PATCH 42/59] fix and test hd keys. --- lib/bcoin/hd.js | 38 ++++++++++++++++++++++++++++++++++++++ lib/bcoin/tx.js | 4 ++-- lib/bcoin/wallet.js | 40 ++++++++++++++++++++++++++++++++++------ test/wallet-test.js | 9 +++++++++ 4 files changed, 83 insertions(+), 8 deletions(-) diff --git a/lib/bcoin/hd.js b/lib/bcoin/hd.js index 4225356d..eb92fdd6 100644 --- a/lib/bcoin/hd.js +++ b/lib/bcoin/hd.js @@ -563,6 +563,44 @@ HDPub.prototype.deriveString = function(path) { }); }; +/** + * Make HD keys behave like elliptic KeyPairs + */ + +[HDPriv, HDPub].forEach(function(HD) { + HD.prototype.validate = function validate() { + return this.pair.validate.apply(this.pair, arguments); + }; + + HD.prototype.getPublic = function getPublic() { + return this.pair.getPublic.apply(this.pair, arguments); + }; + + HD.prototype.getPrivate = function getPrivate() { + return this.pair.getPublic.apply(this.pair, arguments); + }; + + HD.prototype.sign = function sign(msg) { + return this.pair.sign.apply(this.pair, arguments); + }; + + HD.prototype.verify = function verify(msg, signature) { + return this.pair.verify.apply(this.pair, arguments); + }; + + HD.prototype.inspect = function inspect() { + return this.pair.inspect.apply(this.pair, arguments); + }; + + HD.prototype.__defineGetter__('pub', function() { + return this.pair.pub; + }); + + HD.prototype.__defineGetter__('priv', function() { + return this.pair.priv; + }); +}); + /** * Helpers */ diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index b6c4743f..7274944b 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -135,7 +135,7 @@ TX.prototype.signature = function(input, key, type) { var hash = this.subscriptHash(tx.inputs.indexOf(input), s, type); // Sign the transaction with our one input - var signature = bcoin.ecdsa.sign(hash, key).toDER(); + var signature = bcoin.ecdsa.sign(hash, key.priv).toDER(); // Add the sighash as a single byte to the signature signature = signature.concat(type); @@ -204,7 +204,7 @@ TX.prototype.signInput = function(input, key, type) { var hash = this.subscriptHash(this.inputs.indexOf(input), s, type); // Sign the transaction with our one input - var signature = bcoin.ecdsa.sign(hash, key).toDER(); + var signature = bcoin.ecdsa.sign(hash, key.priv).toDER(); // Add the sighash as a single byte to the signature signature = signature.concat(type); diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 8c4b9748..2c6606c6 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -23,25 +23,31 @@ function Wallet(options, passphrase) { if (!options) options = {}; + this.options = options; this.compressed = typeof options.compressed !== 'undefined' ? options.compressed : true; this.storage = options.storage; this.key = null; this.loaded = false; this.lastTs = 0; - this.sharedKeys = options.sharedKeys; if (options.priv instanceof bcoin.hd.priv) { this.hd = options.priv; - this.key = this.hd.pair; + this.key = this.hd; } else if (options.pub instanceof bcoin.hd.pub) { this.hd = options.pub; - this.key = this.hd.pair; + this.key = this.hd; } else if (options.hd) { this.hd = bcoin.hd.priv(options); - this.key = this.hd.pair; + this.key = this.hd; } else if (options.key) { - this.key = options.key; + if ((options.key instanceof bcoin.hd.priv) + || (options.key instanceof bcoin.hd.pub)) { + this.hd = options.key; + this.key = options.key; + } else { + this.key = options.key; + } } else if (options.passphrase) { this.key = bcoin.ecdsa.genKeyPair({ pers: options.scope, @@ -107,7 +113,7 @@ Wallet.prototype._init = function init() { }); }; -Wallet.prototype.multisig = function(options) { +Wallet.prototype.multisig = function multisig(options) { var pub = this.key.getPublic(this.compressed, 'array'); options.type = options.type || options.addressType; @@ -142,6 +148,17 @@ Wallet.prototype.multisig = function(options) { } }; +Wallet.prototype.derive = function derive() { + var options = this.options; + + if (!this.hd) + throw new Error('wallet is not HD'); + + options.priv = this.hd.derive.apply(this.hd, arguments); + + return bcoin.wallet(options); +}; + Wallet.prototype.getPrivateKey = function getPrivateKey(enc) { var priv = this.key.getPrivate(); if (priv) @@ -184,6 +201,17 @@ Wallet.prototype.getPublicKey = function getPublicKey(enc) { return pub; }; +Wallet.prototype._getPublicKey = function _getPublicKey(enc) { + var pub = this.key.getPublic(this.compressed, 'array'); + + if (enc === 'base58') + return utils.toBase58(pub); + else if (enc === 'hex') + return utils.toHex(pub); + else + return pub; +}; + Wallet.prototype.getPublicKeys = function() { var keys = this.sharedKeys.slice().map(utils.toKeyArray); diff --git a/test/wallet-test.js b/test/wallet-test.js index bfbbf100..1b88b002 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -254,11 +254,20 @@ describe('Wallet', function() { }); it('should verify 2-of-3 p2sh tx', function(cb) { + var hd = bcoin.hd.priv(); + var hd1 = hd.derive(0); + var hd2 = hd.derive(1); + var hd3 = hd.derive(2); + // Generate 3 key pairs var key1 = bcoin.ecdsa.genKeyPair(); var key2 = bcoin.ecdsa.genKeyPair(); var key3 = bcoin.ecdsa.genKeyPair(); + // var key1 = hd1; + // var key2 = hd2; + // var key3 = hd3; + // Grab the 3 pubkeys var pub1 = key1.getPublic(true, 'array'); var pub2 = key2.getPublic(true, 'array'); From 71ea61e4670c02b1f144d19cc4aced4e25d77b7d Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 8 Dec 2015 17:34:36 -0800 Subject: [PATCH 43/59] update elliptic. remove ec.curve.n workaround. --- lib/bcoin/hd.js | 11 +---------- package.json | 2 +- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/lib/bcoin/hd.js b/lib/bcoin/hd.js index eb92fdd6..996e518e 100644 --- a/lib/bcoin/hd.js +++ b/lib/bcoin/hd.js @@ -326,16 +326,7 @@ HDPriv.prototype.derive = function(index, hard) { var leftPart = new bn(hash.slice(0, 32)); var chainCode = hash.slice(32, 64); - // XXX This causes a call stack overflow with bn.js@4.0.5 and elliptic@3.0.3 - // Example: new bn(0).mod(ec.curve.n) - //var privateKey = leftPart.add(new bn(this.privateKey)).mod(ec.curve.n).toArray(); - - // Use this as a workaround: - var n = new bn( - 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', - 'hex' - ); - var privateKey = leftPart.add(new bn(this.privateKey)).mod(n).toArray(); + var privateKey = leftPart.add(new bn(this.privateKey)).mod(ec.curve.n).toArray(); return new HDPriv({ // version: this.version, diff --git a/package.json b/package.json index 96b9811f..4e256420 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "dependencies": { "async": "^0.8.0", "bn.js": "^4.5.0", - "elliptic": "^3.0.3", + "elliptic": "^6.0.2", "hash.js": "^1.0.3", "inherits": "^2.0.1" }, From 8a33f2efa1c98408fba4fbbadc5fb8f62dd93471 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 8 Dec 2015 18:43:33 -0800 Subject: [PATCH 44/59] add getFullPublicKey/getOwnPublicKey and use appropriately. --- lib/bcoin/pool.js | 19 +++++++---- lib/bcoin/script.js | 1 + lib/bcoin/wallet.js | 79 +++++++++++++++++++++++++++++++-------------- 3 files changed, 68 insertions(+), 31 deletions(-) diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index 07e127e1..417762b0 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -410,8 +410,11 @@ Pool.prototype._removePeer = function _removePeer(peer) { Pool.prototype.watch = function watch(id) { if (id instanceof bcoin.wallet) { - this.watch(id.getAddress()); - this.watch(id.getPublicKey()); + // this.watch(id.getAddress()); + this.watch(id.getFullHash()); + this.watch(id.getFullPublicKey()); + this.watch(id.getOwnHash()); + this.watch(id.getOwnPublicKey()); return; } @@ -460,8 +463,10 @@ Pool.prototype.unwatch = function unwatch(id) { Pool.prototype.addWallet = function addWallet(w, defaultTs) { if (this.wallets.indexOf(w) !== -1) return false; - this.watch(w.getHash()); - this.watch(w.getPublicKey()); + this.watch(w.getFullHash()); + this.watch(w.getFullPublicKey()); + this.watch(w.getOwnHash()); + this.watch(w.getOwnPublicKey()); var self = this; var e = new EventEmitter(); @@ -492,8 +497,10 @@ Pool.prototype.removeWallet = function removeWallet(w) { if (i == -1) return; this.wallets.splice(i, 1); - this.unwatch(w.getHash()); - this.unwatch(w.getPublicKey()); + this.unwatch(w.getFullHash()); + this.unwatch(w.getFullPublicKey()); + this.unwatch(w.getOwnHash()); + this.unwatch(w.getOwnPublicKey()); } Pool.prototype.search = function search(id, range, e) { diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index b2501be7..55d48bc5 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -593,6 +593,7 @@ script.multisig = function(keys, m, n) { // [ [ n ], 'checkmultisig' ] // ); + // Keys need to be in a predictable order. keys = keys.sort(function(a, b) { return new bn(a).cmp(new bn(b)) > 0; }); diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 2c6606c6..8e012156 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -69,7 +69,7 @@ function Wallet(options, passphrase) { this.multisig(options.multisig || {}); - this.prefix = 'bt/' + this.getAddress() + '/'; + this.prefix = 'bt/' + this.getOwnAddress() + '/'; this.tx = new bcoin.txPool(this); // Just a constants, actually @@ -114,7 +114,7 @@ Wallet.prototype._init = function init() { }; Wallet.prototype.multisig = function multisig(options) { - var pub = this.key.getPublic(this.compressed, 'array'); + var pub = this.getOwnPublicKey(); options.type = options.type || options.addressType; options.keys = options.keys || options.sharedKeys; @@ -185,8 +185,8 @@ Wallet.prototype.getPrivateKey = function getPrivateKey(enc) { } }; -Wallet.prototype.getPublicKey = function getPublicKey(enc) { - var pub = this.key.getPublic(this.compressed, 'array'); +Wallet.prototype.getFullPublicKey = function getFullPublicKey(enc) { + var pub = this.getOwnPublicKey(); if (this.addressType === 'p2sh') { var keys = this.getPublicKeys(); @@ -201,7 +201,7 @@ Wallet.prototype.getPublicKey = function getPublicKey(enc) { return pub; }; -Wallet.prototype._getPublicKey = function _getPublicKey(enc) { +Wallet.prototype.getOwnPublicKey = function getOwnPublicKey(enc) { var pub = this.key.getPublic(this.compressed, 'array'); if (enc === 'base58') @@ -212,13 +212,22 @@ Wallet.prototype._getPublicKey = function _getPublicKey(enc) { return pub; }; +Wallet.prototype.getPublicKey = function getPublicKey(enc) { + return this.getFullPublicKey(enc); +}; + Wallet.prototype.getPublicKeys = function() { + var pub = this.getOwnPublicKey(); + + this.sharedKeys = this.sharedKeys.filter(function(key) { + return !utils.isEqual(key, pub); + }); + var keys = this.sharedKeys.slice().map(utils.toKeyArray); - // if (keys.length < this.m) { - var pub = this.key.getPublic(this.compressed, 'array'); keys.push(pub); + // Keys need to be in a predictable order. keys = keys.sort(function(a, b) { return new bn(a).cmp(new bn(b)) > 0; }); @@ -226,12 +235,28 @@ Wallet.prototype.getPublicKeys = function() { return keys; }; +Wallet.prototype.getFullHash = function getFullHash() { + return utils.ripesha(this.getFullPublicKey()); +}; + +Wallet.prototype.getFullAddress = function getFullAddress() { + return Wallet.hash2addr(this.getFullHash(), this.addressType); +}; + +Wallet.prototype.getOwnHash = function getOwnHash() { + return utils.ripesha(this.getOwnPublicKey()); +}; + +Wallet.prototype.getOwnAddress = function getOwnAddress() { + return Wallet.hash2addr(this.getOwnHash(), this.addressType); +}; + Wallet.prototype.getHash = function getHash() { - return utils.ripesha(this.getPublicKey()); + return utils.ripesha(this.getFullPublicKey()); }; Wallet.prototype.getAddress = function getAddress() { - return Wallet.hash2addr(this.getHash(), this.addressType); + return Wallet.hash2addr(this.getFullHash(), this.addressType); }; Wallet.hash2addr = function hash2addr(hash, version) { @@ -271,8 +296,9 @@ Wallet.prototype.validateAddress = function validateAddress(addr, version) { Wallet.validateAddress = Wallet.prototype.validateAddress; Wallet.prototype.ownOutput = function ownOutput(tx, index) { - var hash = this.getHash(); - var key = this.getPublicKey(); + var scriptHash = this.getFullHash(); + var hash = this.getOwnHash(); + var key = this.getOwnPublicKey(); var outputs = tx.outputs.filter(function(output, i) { if (index !== undefined && index !== i) @@ -289,7 +315,7 @@ Wallet.prototype.ownOutput = function ownOutput(tx, index) { if (bcoin.script.isMultisig(s, key)) return true; - if (bcoin.script.isScripthash(s, hash)) + if (bcoin.script.isScripthash(s, scriptHash)) return true; return false; @@ -301,8 +327,9 @@ Wallet.prototype.ownOutput = function ownOutput(tx, index) { }; Wallet.prototype.ownInput = function ownInput(tx, index) { - var hash = this.getHash(); - var key = this.getPublicKey(); + var scriptHash = this.getFullHash(); + var hash = this.getOwnHash(); + var key = this.getOwnPublicKey(); var inputs = tx.inputs.filter(function(input, i) { if (index !== undefined && index !== i) @@ -322,7 +349,7 @@ Wallet.prototype.ownInput = function ownInput(tx, index) { if (bcoin.script.isMultisig(s, key)) return true; - if (bcoin.script.isScripthash(s, hash)) + if (bcoin.script.isScripthash(s, scriptHash)) return true; return false; @@ -337,7 +364,7 @@ Wallet.prototype.sign = function sign(tx, type, inputs) { if (!type) type = 'all'; - var pub = this.getPublicKey(); + var pub = this.getFullPublicKey(); var key = this.key; inputs = inputs || tx.inputs; @@ -394,13 +421,15 @@ Wallet.prototype.toJSON = function toJSON() { return { v: 1, type: 'wallet', - pub: utils.toBase58(this.key.getPublic(this.compressed, 'array')), + pub: this.getOwnPublicKey('base58'), priv: this.getPrivateKey('base58'), tx: this.tx.toJSON(), - addressType: this.addressType, - sharedKeys: utils.toBase58(this.sharedKeys), - m: this.m, - n: this.n + multisig: { + type: this.addressType, + keys: this.sharedKeys.map(utils.toBase58), + m: this.m, + n: this.n + } }; }; @@ -431,14 +460,14 @@ Wallet.fromJSON = function fromJSON(json) { compressed = pub[0] !== 0x04; } + if (json.multisig && json.multisig.keys) + json.multisig.keys = json.multisig.keys.map(utils.toKeyArray); + var w = new Wallet({ priv: priv, pub: pub, compressed: compressed, - addressType: json.addressType, - sharedKeys: json.sharedKeys, - m: json.m, - n: json.n + multisig: json.multisig }); w.tx.fromJSON(json.tx); From 89f2a0dcc39c427760efea15766c1f2ccfe9813d Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 9 Dec 2015 10:31:44 -0800 Subject: [PATCH 45/59] formatting. make use of helpers in hd. --- lib/bcoin/chain.js | 6 ++---- lib/bcoin/hd.js | 14 ++++++-------- lib/bcoin/peer.js | 3 +-- lib/bcoin/wallet.js | 12 ++++-------- 4 files changed, 13 insertions(+), 22 deletions(-) diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index 7a901a88..025adeeb 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -449,9 +449,8 @@ Chain.prototype.locatorHashes = function(index) { hashes.push(chain[0]); break; } - if (hashes.length >= 10) { + if (hashes.length >= 10) step *= 2; - } } return hashes; @@ -467,9 +466,8 @@ Chain.prototype.getOrphanRoot = function getOrphanRoot(hash) { var orphans = this.orphan.bmap; var orphanRoot = hash; - while (orphans[orphanRoot.prevBlock]) { + while (orphans[orphanRoot.prevBlock]) orphanRoot = orphans[orphanRoot.prevBlock]; - } return orphanRoot; }; diff --git a/lib/bcoin/hd.js b/lib/bcoin/hd.js index 996e518e..6bd63298 100644 --- a/lib/bcoin/hd.js +++ b/lib/bcoin/hd.js @@ -186,7 +186,7 @@ HDPriv.prototype._normalize = function(data, version) { if (data.privateKey.getPrivate) data.privateKey = data.privateKey.getPrivate().toArray(); else if (typeof data.privateKey === 'string') - data.privateKey = utils.toArray(data.privateKey, 'hex'); + data.privateKey = utils.toKeyArray(data.privateKey); } data.publicKey = data.publicKey || data.pub; @@ -194,7 +194,7 @@ HDPriv.prototype._normalize = function(data, version) { if (data.publicKey.getPublic) data.publicKey = data.privateKey.getPublic(true, 'array'); else if (typeof data.publicKey === 'string') - data.publicKey = utils.toArray(data.publicKey, 'hex'); + data.publicKey = utils.toKeyArray(data.publicKey); } if (typeof data.checksum === 'number') { @@ -210,7 +210,7 @@ HDPriv.prototype._seed = function(seed) { if (seed instanceof HDSeed) seed = seed.seed; - if (typeof seed === 'string' && /^[0-9a-f]+$/i.test(seed)) + if (utils.isHex(seed)) seed = utils.toArray(seed, 'hex'); if (seed.length < MIN_ENTROPY || seed.length > MAX_ENTROPY) @@ -316,11 +316,9 @@ HDPriv.prototype.derive = function(index, hard) { var index_ = []; utils.writeU32BE(index_, index, 0); - var data; - if (hard) - data = [0].concat(this.privateKey).concat(index_); - else - data = [].concat(this.publicKey).concat(index_); + var data = hard + ? [0].concat(this.privateKey).concat(index_) + : data = [].concat(this.publicKey).concat(index_); var hash = sha512hmac(data, this.chainCode); var leftPart = new bn(hash.slice(0, 32)); diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index 8b8212c8..b06b8c22 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -466,9 +466,8 @@ Peer.prototype._handleHeaders = function handleHeaders(headers) { this.loadHeaders(this.chain.locatorHashes(), this.chain.getOrphanRoot(hash)); continue; } - if (!this.chain.index.bloom.test(hash, 'hex') || i === headers.length - 1) { + if (!this.chain.index.bloom.test(hash, 'hex') || i === headers.length - 1) this.getData([{ type: 'block', hash: hash }]); - } } } diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 8e012156..6dfd9a33 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -131,21 +131,17 @@ Wallet.prototype.multisig = function multisig(options) { }); // Use p2sh multisig by default - if (!options.addressType && this.sharedKeys.length) { + if (!options.addressType && this.sharedKeys.length) this.addressType = 'p2sh'; - } - if (this.m < 1 || this.m > this.n) { + if (this.m < 1 || this.m > this.n) throw new Error('m ranges between 1 and n'); - } - if (this.n < 1 || this.n > 7) { + if (this.n < 1 || this.n > 7) throw new Error('n ranges between 1 and 7'); - } - if (this.sharedKeys.length < this.m - 1) { + if (this.sharedKeys.length < this.m - 1) throw new Error(this.m + ' public keys required'); - } }; Wallet.prototype.derive = function derive() { From c869578009ef255b8a35036d607adcf4daea731c Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 9 Dec 2015 10:42:36 -0800 Subject: [PATCH 46/59] script: cleanup. --- lib/bcoin/script.js | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 55d48bc5..f38b8036 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -139,8 +139,6 @@ script.execute = function execute(s, stack, tx, index) { stack.alt = stack.alt || []; - // TODO: if statements - for (var pc = 0; pc < s.length; pc++) { var o = s[pc]; @@ -165,6 +163,8 @@ script.execute = function execute(s, stack, tx, index) { // OP_EVAL // if (o === 'nop1') { // var evalScript = script.decode(stack.pop()); + // if (!Array.isArray(evalScript)) + // return false; // var res = script.execute(evalScript, stack, tx, index); // if (!res) // return false; @@ -399,13 +399,6 @@ script.execute = function execute(s, stack, tx, index) { stack.push(n.toArray()); // stack.push(res ? [ 1 ] : []); } - // stack.push(n.toArray()); - // if (op == 'numeqverify') { - // if (n.cmp(0) !== 0) - // stack.pop(); - // else - // return false; - // } break; case 'within': if (stack.length < 3) @@ -464,7 +457,6 @@ script.execute = function execute(s, stack, tx, index) { if (!script.isValidSig(sig)) return false; - // var subscript = input.out.tx.getSubscript(input.out.index); var subscript = s.slice(lastSep + 1); var hash = tx.subscriptHash(index, subscript, type); @@ -506,24 +498,18 @@ script.execute = function execute(s, stack, tx, index) { // Get signatures var succ = 0; - // for (var i = 0, j = 0; i < m && j < n; i++) { for (var i = 0; i < m; i++) { var sig = stack.pop(); var type = sig[sig.length - 1]; if (!constants.rhashType[type & 0x7f]) return false; - // var subscript = input.out.tx.getSubscript(input.out.index); var subscript = s.slice(lastSep + 1); var hash = tx.subscriptHash(index, subscript, type); if (!script.isValidSig(sig)) return false; - // var res = false; - // for (; !res && j < n; j++) - // res = script.verify(hash, sig.slice(0, -1), keys[j]); - // Strict order: var res = script.verify(hash, sig.slice(0, -1), keys.pop()); if (res) From d1ac6e914d17d153b4c21ff0278ea9fb94afd697 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 9 Dec 2015 10:57:18 -0800 Subject: [PATCH 47/59] refactor script.execute. --- lib/bcoin/script.js | 879 +++++++++++++++++++++++++------------------- 1 file changed, 498 insertions(+), 381 deletions(-) diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index f38b8036..300eebf9 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -144,416 +144,533 @@ script.execute = function execute(s, stack, tx, index) { if (Array.isArray(o)) { stack.push(o); - } else if (typeof o === 'number' && o >= 1 && o <= 16) { + continue; + } + + if (typeof o === 'number' && o >= 1 && o <= 16) { stack.push([o]); - } else if (o === 'dup') { - if (stack.length === 0) - return false; + continue; + } - stack.push(stack[stack.length - 1]); - } else if (o === 'drop') { - if (stack.length === 0) - return false; + switch (o) { + case 'dup': { + if (stack.length === 0) + return false; - stack.pop(); - } else if (o === 'nop1' || o === 'nop3' || o === 'nop4' - || o === 'nop5' || o === 'nop6' || o === 'nop7' - || o === 'nop8' || o === 'nop9' || o === 'nop10') { - ; - // OP_EVAL - // if (o === 'nop1') { - // var evalScript = script.decode(stack.pop()); - // if (!Array.isArray(evalScript)) - // return false; - // var res = script.execute(evalScript, stack, tx, index); - // if (!res) - // return false; - // } - } else if (o === 'verify') { - if (stack.length === 0) - return false; - if (new bn(stack[stack.length - 1]).cmp(0) === 0) - return false; - } else if (o === 'return') { - return false; - } else if (o === 'toaltstack') { - if (stack.length === 0) - return false; - stack.alt.push(stack.pop()); - } else if (o === 'fromaltstack') { - if (stack.alt.length === 0) - return false; - stack.push(stack.alt.pop()); - } else if (o === 'ifdup') { - if (stack.length === 0) - return false; - if (new bn(stack[stack.length - 1]).cmp(0) !== 0) - stack.push(new bn(stack[stack.length - 1]).toArray()); - } else if (o === 'depth') { - stack.push(new bn(stack.length).toArray()); - } else if (o === 'nip') { - if (stack.length < 2) - return false; - stack.splice(stack.length - 2, 1); - } else if (o === 'over') { - if (stack.length < 2) - return false; - stack.push(stack[stack.length - 2]); - } else if (o === 'pick' || o === 'roll') { - if (stack.length < 2) - return false; - var n = new bn(stack.pop()).toNumber(); - if (n < 0 || n >= stack.length) - return false; - var v = stack[-n - 1]; - if (o === 'roll') - stack.splice(stack.length - n - 1, 1); - stack.push(v); - } else if (o === 'rot') { - if (stack.length < 3) - return false; - var v3 = stack[stack.length - 3]; - var v2 = stack[stack.length - 2]; - var v1 = stack[stack.length - 1]; - stack[stack.length - 3] = v2; - stack[stack.length - 2] = v3; + stack.push(stack[stack.length - 1]); - v2 = stack[stack.length - 2]; - stack[stack.length - 2] = v1; - stack[stack.length - 1] = v2; - } else if (o === 'swap') { - if (stack.length < 2) + break; + } + case 'drop': { + if (stack.length === 0) + return false; + + stack.pop(); + + break; + } + case 'nop1': + case 'nop3': + case 'nop4': + case 'nop5': + case 'nop6': + case 'nop7': + case 'nop8': + case 'nop9': + case 'nop10': { + ; + // OP_EVAL + // if (o === 'nop1') { + // var evalScript = script.decode(stack.pop()); + // if (!Array.isArray(evalScript)) + // return false; + // var res = script.execute(evalScript, stack, tx, index); + // if (!res) + // return false; + // } + break; + } + case 'verify': { + if (stack.length === 0) + return false; + if (new bn(stack[stack.length - 1]).cmp(0) === 0) + return false; + + break; + } + case 'ret': { return false; - var v2 = stack[stack.length - 2]; - var v1 = stack[stack.length - 1]; - stack[stack.length - 2] = v1; - stack[stack.length - 1] = v2; - } else if (o === 'tuck') { - if (stack.length < 2) - return false; - stack.splice(stack.length - 2, 0, stack[stack.length - 1]); - } else if (o === 'drop2') { - if (stack.length < 2) - return false; - stack.pop(); - stack.pop(); - } else if (o === 'dup2') { - if (stack.length < 2) - return false; - var v1 = stack[stack.length - 1]; - var v2 = stack[stack.length - 2]; - stack.push(v1); - stack.push(v2); - } else if (o === 'dup3') { - if (stack.length < 3) - return false; - var v1 = stack[stack.length - 1]; - var v2 = stack[stack.length - 2]; - var v3 = stack[stack.length - 3]; - stack.push(v1); - stack.push(v2); - stack.push(v3); - } else if (o === 'over2') { - if (stack.length < 4) - return false; - var v1 = stack[stack.length - 4]; - var v2 = stack[stack.length - 3]; - stack.push(v1); - stack.push(v2); - } else if (o === 'rot2') { - if (stack.length < 6) - return false; - var v1 = stack[stack.length - 6]; - var v2 = stack[stack.length - 5]; - stack.splice(stack.length - 6, 2); - stack.push(v1); - stack.push(v2); - } else if (o === 'swap2') { - if (stack.length < 4) - return false; - var v4 = stack[stack.length - 4]; - var v3 = stack[stack.length - 3]; - var v2 = stack[stack.length - 2]; - var v1 = stack[stack.length - 1]; - stack[stack.length - 4] = v2; - stack[stack.length - 2] = v4; - stack[stack.length - 3] = v1; - stack[stack.length - 1] = v3; - } else if (o === 'size') { - if (stack.length < 1) - return false; - stack.push(new bn(stack[stack.length - 1].length || 0).toArray()); - } else if (o === 'add1' - || o === 'sub1' - || o === 'negate' - || o === 'abs' - || o === 'not' - || o === 'noteq0') { - if (stack.length < 1) - return false; - var n = new bn(stack.pop()); - switch (o) { - case 'add1': - n.iadd(1); - break; - case 'sub1': - n.isub(1); - break; - case 'negate': - n = n.neg(); - break; - case 'abs': - if (n.cmp(0) < 0) + } + case 'toaltstack': { + if (stack.length === 0) + return false; + stack.alt.push(stack.pop()); + + break; + } + case 'fromaltstack': { + if (stack.alt.length === 0) + return false; + stack.push(stack.alt.pop()); + + break; + } + case 'ifdup': { + if (stack.length === 0) + return false; + if (new bn(stack[stack.length - 1]).cmp(0) !== 0) + stack.push(new bn(stack[stack.length - 1]).toArray()); + + break; + } + case 'depth': { + stack.push(new bn(stack.length).toArray()); + + break; + } + case 'nip': { + if (stack.length < 2) + return false; + stack.splice(stack.length - 2, 1); + + break; + } + case 'over': { + if (stack.length < 2) + return false; + stack.push(stack[stack.length - 2]); + + break; + } + case 'pick': + case 'roll': { + if (stack.length < 2) + return false; + var n = new bn(stack.pop()).toNumber(); + if (n < 0 || n >= stack.length) + return false; + var v = stack[-n - 1]; + if (o === 'roll') + stack.splice(stack.length - n - 1, 1); + stack.push(v); + + break; + } + case 'rot': { + if (stack.length < 3) + return false; + var v3 = stack[stack.length - 3]; + var v2 = stack[stack.length - 2]; + var v1 = stack[stack.length - 1]; + stack[stack.length - 3] = v2; + stack[stack.length - 2] = v3; + + v2 = stack[stack.length - 2]; + stack[stack.length - 2] = v1; + stack[stack.length - 1] = v2; + + break; + } + case 'swap': { + if (stack.length < 2) + return false; + var v2 = stack[stack.length - 2]; + var v1 = stack[stack.length - 1]; + stack[stack.length - 2] = v1; + stack[stack.length - 1] = v2; + + break; + } + case 'tuck': { + if (stack.length < 2) + return false; + stack.splice(stack.length - 2, 0, stack[stack.length - 1]); + + break; + } + case 'drop2': { + if (stack.length < 2) + return false; + stack.pop(); + stack.pop(); + + break; + } + case 'dup2': { + if (stack.length < 2) + return false; + var v1 = stack[stack.length - 1]; + var v2 = stack[stack.length - 2]; + stack.push(v1); + stack.push(v2); + + break; + } + case 'dup3': { + if (stack.length < 3) + return false; + var v1 = stack[stack.length - 1]; + var v2 = stack[stack.length - 2]; + var v3 = stack[stack.length - 3]; + stack.push(v1); + stack.push(v2); + stack.push(v3); + + break; + } + case 'over2': { + if (stack.length < 4) + return false; + var v1 = stack[stack.length - 4]; + var v2 = stack[stack.length - 3]; + stack.push(v1); + stack.push(v2); + + break; + } + case 'rot2': { + if (stack.length < 6) + return false; + var v1 = stack[stack.length - 6]; + var v2 = stack[stack.length - 5]; + stack.splice(stack.length - 6, 2); + stack.push(v1); + stack.push(v2); + + break; + } + case 'swap2': { + if (stack.length < 4) + return false; + var v4 = stack[stack.length - 4]; + var v3 = stack[stack.length - 3]; + var v2 = stack[stack.length - 2]; + var v1 = stack[stack.length - 1]; + stack[stack.length - 4] = v2; + stack[stack.length - 2] = v4; + stack[stack.length - 3] = v1; + stack[stack.length - 1] = v3; + + break; + } + case 'size': { + if (stack.length < 1) + return false; + stack.push(new bn(stack[stack.length - 1].length || 0).toArray()); + + break; + } + case 'add1': + case 'sub1': + case 'negate': + case 'abs': + case 'not': + case 'noteq0': { + if (stack.length < 1) + return false; + var n = new bn(stack.pop()); + switch (o) { + case 'add1': + n.iadd(1); + break; + case 'sub1': + n.isub(1); + break; + case 'negate': n = n.neg(); - break; - case 'not': - n = n.cmp(0) === 0; - break; - case 'noteq0': - n = n.cmp(0) !== 0; - break; - default: - return false; - } - stack.push(n.toArray()); - } else if (o === 'add' - || o === 'sub' - || o === 'booland' - || o === 'boolor' - || o === 'numeq' - || o === 'numeqverify' - || o === 'numneq' - || o === 'lt' - || o === 'gt' - || o === 'lte' - || o === 'gte' - || o === 'min' - || o === 'max') { - switch (o) { - case 'add': - case 'sub': - case 'booland': - case 'boolor': - case 'numeq': - case 'numeqverify': - case 'numneq': - case 'lt': - case 'gt': - case 'lte': - case 'gte': - case 'min': - case 'max': - if (stack.length < 2) + break; + case 'abs': + if (n.cmp(0) < 0) + n = n.neg(); + break; + case 'not': + n = n.cmp(0) === 0; + break; + case 'noteq0': + n = n.cmp(0) !== 0; + break; + default: return false; - var n2 = new bn(stack.pop()); - var n1 = new bn(stack.pop()); - var n = new bn(0); - switch (o) { - case 'add': - n = n1.add(b2); - break; - case 'sub': - n = n1.sub(n2); - break; - case 'booland': - n = n1.cmp(0) !== 0 && n2.cmp(0) !== 0; - break; - case 'boolor': - n = n1.cmp(0) !== 0 || n2.cmp(0) !== 0; - break; - case 'numeq': - n = n1.cmp(n2) === 0; - break; - case 'numeqverify': - n = n1.cmp(n2) === 0; - break; - case 'numneq': - n = n1.cmp(n2) !== 0; - break; - case 'lt': - n = n1.cmp(n2) < 0; - break; - case 'gt': - n = n1.cmp(n2) > 0; - break; - case 'lte': - n = n1.cmp(n2) <= 0; - break; - case 'gte': - n = n1.cmp(n2) >= 0; - break; - case 'min': - n = n1.cmp(n2) < 0 ? n1 : n2; - break; - case 'max': - n = n1.cmp(n2) > 0 ? n1 : n2; - break; - default: + } + stack.push(n.toArray()); + + break; + } + case 'add': + case 'sub': + case 'booland': + case 'boolor': + case 'numeq': + case 'numeqverify': + case 'numneq': + case 'lt': + case 'gt': + case 'lte': + case 'gte': + case 'min': + case 'max': { + switch (o) { + case 'add': + case 'sub': + case 'booland': + case 'boolor': + case 'numeq': + case 'numeqverify': + case 'numneq': + case 'lt': + case 'gt': + case 'lte': + case 'gte': + case 'min': + case 'max': + if (stack.length < 2) return false; - } - var res = n.cmp(0) !== 0; - if (o === 'numeqverify') { - if (!res) + var n2 = new bn(stack.pop()); + var n1 = new bn(stack.pop()); + var n = new bn(0); + switch (o) { + case 'add': + n = n1.add(b2); + break; + case 'sub': + n = n1.sub(n2); + break; + case 'booland': + n = n1.cmp(0) !== 0 && n2.cmp(0) !== 0; + break; + case 'boolor': + n = n1.cmp(0) !== 0 || n2.cmp(0) !== 0; + break; + case 'numeq': + n = n1.cmp(n2) === 0; + break; + case 'numeqverify': + n = n1.cmp(n2) === 0; + break; + case 'numneq': + n = n1.cmp(n2) !== 0; + break; + case 'lt': + n = n1.cmp(n2) < 0; + break; + case 'gt': + n = n1.cmp(n2) > 0; + break; + case 'lte': + n = n1.cmp(n2) <= 0; + break; + case 'gte': + n = n1.cmp(n2) >= 0; + break; + case 'min': + n = n1.cmp(n2) < 0 ? n1 : n2; + break; + case 'max': + n = n1.cmp(n2) > 0 ? n1 : n2; + break; + default: + return false; + } + var res = n.cmp(0) !== 0; + if (o === 'numeqverify') { + if (!res) + return false; + } else { + stack.push(n.toArray()); + // stack.push(res ? [ 1 ] : []); + } + break; + case 'within': + if (stack.length < 3) return false; - } else { - stack.push(n.toArray()); - // stack.push(res ? [ 1 ] : []); - } - break; - case 'within': - if (stack.length < 3) + var n3 = new bn(stack.pop()); + var n2 = new bn(stack.pop()); + var n1 = new bn(stack.pop()); + var val = n2.cmp(n1) <= 0 && n1.cmp(n3) < 0; + stack.push(val.cmp(0) !== 0 ? [ 1 ] : []); + break; + } + + break; + } + case 'codesep': { + lastSep = pc; + + break; + } + case 'ripemd160': { + if (stack.length === 0) + return false; + stack.push(utils.ripemd160(stack.pop())); + + break; + } + case 'sha1': { + if (stack.length === 0) + return false; + stack.push(utils.sha1(stack.pop())); + + break; + } + case 'sha256': { + if (stack.length === 0) + return false; + stack.push(utils.sha256(stack.pop())); + + break; + } + case 'hash256': { + if (stack.length === 0) + return false; + stack.push(utils.dsha256(stack.pop())); + + break; + } + case 'hash160': { + if (stack.length === 0) + return false; + + stack.push(utils.ripesha(stack.pop())); + + break; + } + case 'eqverify': + case 'eq': { + if (stack.length < 2) + return false; + + var res = utils.isEqual(stack.pop(), stack.pop()); + if (o === 'eqverify') { + if (!res) return false; - var n3 = new bn(stack.pop()); - var n2 = new bn(stack.pop()); - var n1 = new bn(stack.pop()); - var val = n2.cmp(n1) <= 0 && n1.cmp(n3) < 0; - stack.push(val.cmp(0) !== 0 ? [ 1 ] : []); - break; + } else { + stack.push(res ? [ 1 ] : []); + } + + break; } - } else if (o === 'codesep') { - lastSep = pc; - } else if (o === 'ripemd160') { - if (stack.length === 0) - return false; - stack.push(utils.ripemd160(stack.pop())); - } else if (o === 'sha1') { - if (stack.length === 0) - return false; - stack.push(utils.sha1(stack.pop())); - } else if (o === 'sha256') { - if (stack.length === 0) - return false; - stack.push(utils.sha256(stack.pop())); - } else if (o === 'hash256') { - if (stack.length === 0) - return false; - stack.push(utils.dsha256(stack.pop())); - } else if (o === 'hash160') { - if (stack.length === 0) - return false; - - stack.push(utils.ripesha(stack.pop())); - } else if (o === 'eqverify' || o === 'eq') { - if (stack.length < 2) - return false; - - var res = utils.isEqual(stack.pop(), stack.pop()); - if (o === 'eqverify') { - if (!res) - return false; - } else { - stack.push(res ? [ 1 ] : []); - } - } else if (o === 'checksigverify' || o === 'checksig') { - if (!tx || stack.length < 2) - return false; - - var pub = stack.pop(); - var sig = stack.pop(); - var type = sig[sig.length - 1]; - if (!constants.rhashType[type & 0x7f]) - return false; - - if (!script.isValidSig(sig)) - return false; - - var subscript = s.slice(lastSep + 1); - var hash = tx.subscriptHash(index, subscript, type); - - var res = script.verify(hash, sig.slice(0, -1), pub); - if (o === 'checksigverify') { - if (!res) - return false; - } else { - stack.push(res ? [ 1 ] : []); - } - } else if (o === 'checkmultisigverify' || o === 'checkmultisig') { - if (!tx || stack.length < 3) - return false; - - var n = stack.pop(); - if (n.length !== 1 || !(1 <= n[0] && n[0] <= 3)) - return false; - n = n[0]; - - if (stack.length < n + 1) - return false; - - var keys = []; - for (var i = 0; i < n; i++) { - var key = stack.pop(); - if (!(33 <= key.length && key.length <= 65)) + case 'checksigverify': + case 'checksig': { + if (!tx || stack.length < 2) return false; - keys.push(key); - } - - var m = stack.pop(); - if (m.length !== 1 || !(1 <= m[0] && m[0] <= n)) - return false; - m = m[0]; - - if (stack.length < m + 1) - return false; - - // Get signatures - var succ = 0; - for (var i = 0; i < m; i++) { + var pub = stack.pop(); var sig = stack.pop(); var type = sig[sig.length - 1]; if (!constants.rhashType[type & 0x7f]) return false; - var subscript = s.slice(lastSep + 1); - var hash = tx.subscriptHash(index, subscript, type); - if (!script.isValidSig(sig)) return false; - // Strict order: - var res = script.verify(hash, sig.slice(0, -1), keys.pop()); - if (res) - succ++; + var subscript = s.slice(lastSep + 1); + var hash = tx.subscriptHash(index, subscript, type); + + var res = script.verify(hash, sig.slice(0, -1), pub); + if (o === 'checksigverify') { + if (!res) + return false; + } else { + stack.push(res ? [ 1 ] : []); + } + + break; } - - // Extra value - stack.pop(); - - var res = succ >= m; - if (o === 'checkmultisigverify') { - if (!res) + case 'checkmultisigverify': + case 'checkmultisig': { + if (!tx || stack.length < 3) return false; - } else { - stack.push(res ? [ 1 ] : []); + + var n = stack.pop(); + if (n.length !== 1 || !(1 <= n[0] && n[0] <= 3)) + return false; + n = n[0]; + + if (stack.length < n + 1) + return false; + + var keys = []; + for (var i = 0; i < n; i++) { + var key = stack.pop(); + if (!(33 <= key.length && key.length <= 65)) + return false; + + keys.push(key); + } + + var m = stack.pop(); + if (m.length !== 1 || !(1 <= m[0] && m[0] <= n)) + return false; + m = m[0]; + + if (stack.length < m + 1) + return false; + + // Get signatures + var succ = 0; + for (var i = 0; i < m; i++) { + var sig = stack.pop(); + var type = sig[sig.length - 1]; + if (!constants.rhashType[type & 0x7f]) + return false; + + var subscript = s.slice(lastSep + 1); + var hash = tx.subscriptHash(index, subscript, type); + + if (!script.isValidSig(sig)) + return false; + + // Strict order: + var res = script.verify(hash, sig.slice(0, -1), keys.pop()); + if (res) + succ++; + } + + // Extra value + stack.pop(); + + var res = succ >= m; + if (o === 'checkmultisigverify') { + if (!res) + return false; + } else { + stack.push(res ? [ 1 ] : []); + } + + break; } - } else if (o === 'checklocktimeverify') { - // input: [[], sig1, sig2, 1] - // prev_out: [[lock], 'checklocktimeverify', 'drop', - // 'dup', 'hash160', pubkey, 'equalverify', 'checksig'] - if (stack.length === 0) - return false; + case 'checklocktimeverify': { + // input: [[], sig1, sig2, 1] + // prev_out: [[lock], 'checklocktimeverify', 'drop', + // 'dup', 'hash160', pubkey, 'equalverify', 'checksig'] + if (stack.length === 0) + return false; - var lock = new bn(stack[stack.length - 1]).toNumber(); + var lock = new bn(stack[stack.length - 1]).toNumber(); - if (lock < 0) - return false; + if (lock < 0) + return false; - var threshold = constants.locktimeThreshold; - if (!( - (tx.lock < threshold && lock < threshold) || - (tx.lock >= threshold && lock >= threshold) - )) { + var threshold = constants.locktimeThreshold; + if (!( + (tx.lock < threshold && lock < threshold) || + (tx.lock >= threshold && lock >= threshold) + )) { + return false; + } + + if (lock > tx.lock) + return false; + + if (input.seq === 0xffffffff) + return false; + + break; + } + default: { + // Unknown operation return false; } - - if (lock > tx.lock) - return false; - - if (input.seq === 0xffffffff) - return false; - } else { - // Unknown operation - return false; } } From a0a391e89ef1d9d20292db9f74089cc2388ea39a Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 9 Dec 2015 11:55:05 -0800 Subject: [PATCH 48/59] add if/notif/else/endif opcodes. --- lib/bcoin/script.js | 64 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 300eebf9..9e6886a5 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -130,7 +130,30 @@ script.verify = function verify(hash, sig, pub) { } }; +script._next = function(to, s, pc) { + var depth = 0; + while (s[pc]) { + var o = s[pc]; + if (o === 'if_' || o === 'notif') + depth++; + else if (o === 'else') + depth--; + else if (o === 'endif') + depth--; + if (depth < 0) + break; + if (depth === 0 && o === to) + return pc; + if (o === 'else') + depth++; + pc++; + } + return -1; +}; + script.execute = function execute(s, stack, tx, index) { + s = s.slice(); + if (s.length > 10000) return false; @@ -153,6 +176,47 @@ script.execute = function execute(s, stack, tx, index) { } switch (o) { + case 'if_': + case 'notif': { + var val = false; + if (stack.length < 1) + return false; + var v = stack.pop(); + val = new bn(v).cmp(0) !== 0; + if (o === 'notif') + val = !val; + var if_ = pc; + var else_ = script._next('else_', s, pc); + var endif = script._next('endif', s, pc); + // Splice out the statement blocks we don't need + if (val) { + if (endif === -1) + return false; + if (else_ === -1) { + s.splice(endif, 1); + s.splice(if_, 1); + } else { + s.splice(else_, (endif - else_) + 1); + s.splice(if_, 1); + } + } else { + if (endif === -1) + return false; + if (else_ === -1) + s.splice(if_, (endif - if_) + 1); + else + s.splice(if_, (else_ - if_) + 1); + } + // Subtract one since we removed the if/notif opcode + pc--; + break; + } + case 'else_': { + return false; + } + case 'endif': { + return false; + } case 'dup': { if (stack.length === 0) return false; From 2412c0999b3f0ff7de1a8d3cf9c43a2b2f5260e0 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 9 Dec 2015 11:56:08 -0800 Subject: [PATCH 49/59] potential tx.verify fix. --- lib/bcoin/tx.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 7274944b..108b7477 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -437,6 +437,9 @@ TX.prototype.verify = function verify(index, force) { if (!res) return false; + // Might be necessary for arithmetic: + // if (stack.length === 0 || new bn(stack.pop()).cmp(0) !== 0) + if (stack.length === 0 || !utils.isEqual(stack.pop(), [ 1 ])) return false; From e605fe538834b913ac74e52e9d9777717929dcf8 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 9 Dec 2015 11:57:54 -0800 Subject: [PATCH 50/59] whitespace. --- lib/bcoin/script.js | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 9e6886a5..1f9d3f13 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -220,17 +220,13 @@ script.execute = function execute(s, stack, tx, index) { case 'dup': { if (stack.length === 0) return false; - stack.push(stack[stack.length - 1]); - break; } case 'drop': { if (stack.length === 0) return false; - stack.pop(); - break; } case 'nop1': @@ -259,7 +255,6 @@ script.execute = function execute(s, stack, tx, index) { return false; if (new bn(stack[stack.length - 1]).cmp(0) === 0) return false; - break; } case 'ret': { @@ -269,14 +264,12 @@ script.execute = function execute(s, stack, tx, index) { if (stack.length === 0) return false; stack.alt.push(stack.pop()); - break; } case 'fromaltstack': { if (stack.alt.length === 0) return false; stack.push(stack.alt.pop()); - break; } case 'ifdup': { @@ -284,26 +277,22 @@ script.execute = function execute(s, stack, tx, index) { return false; if (new bn(stack[stack.length - 1]).cmp(0) !== 0) stack.push(new bn(stack[stack.length - 1]).toArray()); - break; } case 'depth': { stack.push(new bn(stack.length).toArray()); - break; } case 'nip': { if (stack.length < 2) return false; stack.splice(stack.length - 2, 1); - break; } case 'over': { if (stack.length < 2) return false; stack.push(stack[stack.length - 2]); - break; } case 'pick': @@ -317,7 +306,6 @@ script.execute = function execute(s, stack, tx, index) { if (o === 'roll') stack.splice(stack.length - n - 1, 1); stack.push(v); - break; } case 'rot': { @@ -328,11 +316,9 @@ script.execute = function execute(s, stack, tx, index) { var v1 = stack[stack.length - 1]; stack[stack.length - 3] = v2; stack[stack.length - 2] = v3; - v2 = stack[stack.length - 2]; stack[stack.length - 2] = v1; stack[stack.length - 1] = v2; - break; } case 'swap': { @@ -342,14 +328,12 @@ script.execute = function execute(s, stack, tx, index) { var v1 = stack[stack.length - 1]; stack[stack.length - 2] = v1; stack[stack.length - 1] = v2; - break; } case 'tuck': { if (stack.length < 2) return false; stack.splice(stack.length - 2, 0, stack[stack.length - 1]); - break; } case 'drop2': { @@ -357,7 +341,6 @@ script.execute = function execute(s, stack, tx, index) { return false; stack.pop(); stack.pop(); - break; } case 'dup2': { @@ -367,7 +350,6 @@ script.execute = function execute(s, stack, tx, index) { var v2 = stack[stack.length - 2]; stack.push(v1); stack.push(v2); - break; } case 'dup3': { @@ -379,7 +361,6 @@ script.execute = function execute(s, stack, tx, index) { stack.push(v1); stack.push(v2); stack.push(v3); - break; } case 'over2': { @@ -389,7 +370,6 @@ script.execute = function execute(s, stack, tx, index) { var v2 = stack[stack.length - 3]; stack.push(v1); stack.push(v2); - break; } case 'rot2': { @@ -400,7 +380,6 @@ script.execute = function execute(s, stack, tx, index) { stack.splice(stack.length - 6, 2); stack.push(v1); stack.push(v2); - break; } case 'swap2': { @@ -414,14 +393,12 @@ script.execute = function execute(s, stack, tx, index) { stack[stack.length - 2] = v4; stack[stack.length - 3] = v1; stack[stack.length - 1] = v3; - break; } case 'size': { if (stack.length < 1) return false; stack.push(new bn(stack[stack.length - 1].length || 0).toArray()); - break; } case 'add1': @@ -457,7 +434,6 @@ script.execute = function execute(s, stack, tx, index) { return false; } stack.push(n.toArray()); - break; } case 'add': @@ -559,50 +535,42 @@ script.execute = function execute(s, stack, tx, index) { } case 'codesep': { lastSep = pc; - break; } case 'ripemd160': { if (stack.length === 0) return false; stack.push(utils.ripemd160(stack.pop())); - break; } case 'sha1': { if (stack.length === 0) return false; stack.push(utils.sha1(stack.pop())); - break; } case 'sha256': { if (stack.length === 0) return false; stack.push(utils.sha256(stack.pop())); - break; } case 'hash256': { if (stack.length === 0) return false; stack.push(utils.dsha256(stack.pop())); - break; } case 'hash160': { if (stack.length === 0) return false; - stack.push(utils.ripesha(stack.pop())); - break; } case 'eqverify': case 'eq': { if (stack.length < 2) return false; - var res = utils.isEqual(stack.pop(), stack.pop()); if (o === 'eqverify') { if (!res) @@ -610,7 +578,6 @@ script.execute = function execute(s, stack, tx, index) { } else { stack.push(res ? [ 1 ] : []); } - break; } case 'checksigverify': From d45350ede9d399fdb3b0bf6d1676d65c38c7f7e6 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 9 Dec 2015 12:00:35 -0800 Subject: [PATCH 51/59] refactor script.execute order of opcodes. --- lib/bcoin/script.js | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 1f9d3f13..045c73f3 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -217,18 +217,6 @@ script.execute = function execute(s, stack, tx, index) { case 'endif': { return false; } - case 'dup': { - if (stack.length === 0) - return false; - stack.push(stack[stack.length - 1]); - break; - } - case 'drop': { - if (stack.length === 0) - return false; - stack.pop(); - break; - } case 'nop1': case 'nop3': case 'nop4': @@ -238,7 +226,6 @@ script.execute = function execute(s, stack, tx, index) { case 'nop8': case 'nop9': case 'nop10': { - ; // OP_EVAL // if (o === 'nop1') { // var evalScript = script.decode(stack.pop()); @@ -283,6 +270,18 @@ script.execute = function execute(s, stack, tx, index) { stack.push(new bn(stack.length).toArray()); break; } + case 'drop': { + if (stack.length === 0) + return false; + stack.pop(); + break; + } + case 'dup': { + if (stack.length === 0) + return false; + stack.push(stack[stack.length - 1]); + break; + } case 'nip': { if (stack.length < 2) return false; From 79a594716ace5a021de4c93ba123043f2e660775 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 9 Dec 2015 12:02:35 -0800 Subject: [PATCH 52/59] reorder opcode handlers. move op_eval (disabled). --- lib/bcoin/script.js | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 045c73f3..961e14a7 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -176,6 +176,17 @@ script.execute = function execute(s, stack, tx, index) { } switch (o) { + case 'nop1': + case 'nop3': + case 'nop4': + case 'nop5': + case 'nop6': + case 'nop7': + case 'nop8': + case 'nop9': + case 'nop10': { + break; + } case 'if_': case 'notif': { var val = false; @@ -217,26 +228,6 @@ script.execute = function execute(s, stack, tx, index) { case 'endif': { return false; } - case 'nop1': - case 'nop3': - case 'nop4': - case 'nop5': - case 'nop6': - case 'nop7': - case 'nop8': - case 'nop9': - case 'nop10': { - // OP_EVAL - // if (o === 'nop1') { - // var evalScript = script.decode(stack.pop()); - // if (!Array.isArray(evalScript)) - // return false; - // var res = script.execute(evalScript, stack, tx, index); - // if (!res) - // return false; - // } - break; - } case 'verify': { if (stack.length === 0) return false; @@ -670,6 +661,7 @@ script.execute = function execute(s, stack, tx, index) { break; } case 'checklocktimeverify': { + // OP_CHECKLOCKTIMEVERIFY = OP_NOP2 // input: [[], sig1, sig2, 1] // prev_out: [[lock], 'checklocktimeverify', 'drop', // 'dup', 'hash160', pubkey, 'equalverify', 'checksig'] @@ -697,6 +689,17 @@ script.execute = function execute(s, stack, tx, index) { break; } + case 'eval_': { + // OP_EVAL = OP_NOP1 + // var evalScript = script.decode(stack.pop()); + // if (!Array.isArray(evalScript)) + // return false; + // var res = script.execute(evalScript, stack, tx, index); + // if (!res) + // return false; + // break; + return false; + } default: { // Unknown operation return false; From 2fd1d171f2cdaaea353d60a0a6ddb088d1d0fcf2 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 9 Dec 2015 12:11:32 -0800 Subject: [PATCH 53/59] handle op_1negate. --- lib/bcoin/protocol/constants.js | 9 ++------- lib/bcoin/script.js | 6 +++--- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/lib/bcoin/protocol/constants.js b/lib/bcoin/protocol/constants.js index ba25f547..25075f09 100644 --- a/lib/bcoin/protocol/constants.js +++ b/lib/bcoin/protocol/constants.js @@ -57,7 +57,7 @@ exports.opcodes = { pushdata1: 0x4c, pushdata2: 0x4d, pushdata4: 0x4e, - negate1: 0x4f, + // negate1: 0x4f, nop1: 0x61, if_: 0x63, @@ -141,18 +141,13 @@ exports.opcodes = { checklocktimeverify: 0xb1 }; +exports.opcodes['-1'] = 0x50 + -1; for (var i = 1; i <= 16; i++) exports.opcodes[i] = 0x50 + i; for (var i = 0; i <= 7; i++) exports.opcodes['nop' + (i + 3)] = 0xb2 + i; -// exports.opcodes['false'] = exports.opcodes['0']; -// exports.opcodes['true'] = exports.opcodes['1']; - -// exports.opcodes['if'] = exports.opcodes.if_; -// exports.opcodes['else'] = exports.opcodes.else_; - exports.opcodesByVal = new Array(256); Object.keys(exports.opcodes).forEach(function(name) { exports.opcodesByVal[exports.opcodes[name]] = name; diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 961e14a7..7e6dfd6d 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -25,8 +25,8 @@ script.decode = function decode(s) { continue; } - // Raw number - if (b >= 0x51 && b <= 0x60) { + // Raw number (-1 and 1-16) + if (b === 0x4f || (b >= 0x51 && b <= 0x60)) { opcodes.push(b - 0x50); continue; } @@ -170,7 +170,7 @@ script.execute = function execute(s, stack, tx, index) { continue; } - if (typeof o === 'number' && o >= 1 && o <= 16) { + if (o === -1 || (o >= 1 && o <= 16)) { stack.push([o]); continue; } From 690fe7163a5376efa8ea7611fa7cf096c419a631 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 9 Dec 2015 12:24:20 -0800 Subject: [PATCH 54/59] better handling of `[]` when it is OP_0. --- lib/bcoin/script.js | 8 ++++---- lib/bcoin/tx.js | 26 ++++++++++++++++---------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 7e6dfd6d..1eb33d58 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -605,7 +605,7 @@ script.execute = function execute(s, stack, tx, index) { var n = stack.pop(); if (n.length !== 1 || !(1 <= n[0] && n[0] <= 3)) return false; - n = n[0]; + n = n[0] || 0; if (stack.length < n + 1) return false; @@ -622,7 +622,7 @@ script.execute = function execute(s, stack, tx, index) { var m = stack.pop(); if (m.length !== 1 || !(1 <= m[0] && m[0] <= n)) return false; - m = m[0]; + m = m[0] || 0; if (stack.length < m + 1) return false; @@ -782,7 +782,7 @@ script.isMultisig = function isMultisig(s, key) { m = [m]; if (!Array.isArray(m) || m.length !== 1) return false; - m = m[0]; + m = m[0] || 0; if (s[s.length - 1] !== 'checkmultisig') return false; @@ -792,7 +792,7 @@ script.isMultisig = function isMultisig(s, key) { n = [n]; if (!Array.isArray(n) || n.length !== 1) return false; - n = n[0]; + n = n[0] || 0; if (n + 3 !== s.length) return false; diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 108b7477..f0149c61 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -168,7 +168,7 @@ TX.prototype.scriptInput = function(input, pub) { var n = s[s.length - 2]; // If using pushdata instead of OP_1-16: if (Array.isArray(n)) - n = n[0]; + n = n[0] || 0; for (var i = 0; i < n; i++) input.script[i + 1] = []; return; @@ -178,7 +178,11 @@ TX.prototype.scriptInput = function(input, pub) { // p2sh format: OP_FALSE [sig-1] [sig-2] ... [redeem-script] if (bcoin.script.isScripthash(s)) { input.script = [ [] ]; - var n = pub[pub.length - 2] - constants.opcodes['1'] + 1; + var redeem = bcoin.script.decode(pub); + var n = redeem[redeem.length - 2]; + // If using pushdata instead of OP_1-16: + if (Array.isArray(n)) + n = n[0] || 0; for (var i = 0; i < n; i++) input.script[i + 1] = []; // P2SH requires the redeem script after signatures @@ -230,12 +234,9 @@ TX.prototype.signInput = function(input, key, type) { } var m = redeem[0]; - var n = redeem[s.length - 2]; // If using pushdata instead of OP_1-16: if (Array.isArray(m)) - m = m[0]; - if (Array.isArray(n)) - n = n[0]; + m = m[0] || 0; var keys = redeem.slice(1, -2); var pub = key.getPublic(true, 'array'); @@ -258,7 +259,7 @@ TX.prototype.signInput = function(input, key, type) { // and count the total number of signatures. var totalSigs = 0; for (var i = 1; i < len; i++) { - if (input.script[i].length) { + if (Array.isArray(input.script[i]) && input.script[i].length) { totalSigs++; continue; } @@ -274,7 +275,7 @@ TX.prototype.signInput = function(input, key, type) { // All signatures added. Finalize by removing empty slots. if (totalSigs >= m) { for (var i = len - 1; i >= 1; i--) { - if (!input.script[i].length) + if (Array.isArray(input.script[i]) && !input.script[i].length) input.script.splice(i, 1); } } @@ -494,7 +495,7 @@ TX.prototype.maxSize = function maxSize() { var m = s[0]; // If using pushdata instead of OP_1-16: if (Array.isArray(m)) - m = m[0]; + m = m[0] || 0; assert(m >= 1 && m <= 3); size += 74 * m; return; @@ -504,9 +505,14 @@ TX.prototype.maxSize = function maxSize() { var script = this.inputs[i].script; var redeem, m, n; if (script.length) { - redeem = script[script.length - 1]; + redeem = bcoin.script.decode(script[script.length - 1]); m = redeem[0]; n = redeem[redeem.length - 2]; + // If using pushdata instead of OP_1-16: + if (Array.isArray(m)) + m = m[0] || 0; + if (Array.isArray(n)) + n = n[0] || 0; } else { // May end up in a higher fee if we // do not have the redeem script available. From 72d9a9377344ea923084871fb2ab8bb4342400d1 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 9 Dec 2015 14:35:23 -0800 Subject: [PATCH 55/59] fix and test if statements. fix comparisons. --- lib/bcoin/script.js | 35 ++++++++++++++++++----------------- test/script-test.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 17 deletions(-) diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 1eb33d58..e8c45f58 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -136,7 +136,7 @@ script._next = function(to, s, pc) { var o = s[pc]; if (o === 'if_' || o === 'notif') depth++; - else if (o === 'else') + else if (o === 'else_') depth--; else if (o === 'endif') depth--; @@ -144,7 +144,7 @@ script._next = function(to, s, pc) { break; if (depth === 0 && o === to) return pc; - if (o === 'else') + if (o === 'else_') depth++; pc++; } @@ -157,7 +157,6 @@ script.execute = function execute(s, stack, tx, index) { if (s.length > 10000) return false; - var input = tx.inputs[index]; var lastSep = -1; stack.alt = stack.alt || []; @@ -193,7 +192,7 @@ script.execute = function execute(s, stack, tx, index) { if (stack.length < 1) return false; var v = stack.pop(); - val = new bn(v).cmp(0) !== 0; + val = new bn(v).cmpn(0) !== 0; if (o === 'notif') val = !val; var if_ = pc; @@ -213,10 +212,12 @@ script.execute = function execute(s, stack, tx, index) { } else { if (endif === -1) return false; - if (else_ === -1) + if (else_ === -1) { s.splice(if_, (endif - if_) + 1); - else + } else { + s.splice(endif, 1); s.splice(if_, (else_ - if_) + 1); + } } // Subtract one since we removed the if/notif opcode pc--; @@ -231,7 +232,7 @@ script.execute = function execute(s, stack, tx, index) { case 'verify': { if (stack.length === 0) return false; - if (new bn(stack[stack.length - 1]).cmp(0) === 0) + if (new bn(stack[stack.length - 1]).cmpn(0) === 0) return false; break; } @@ -253,7 +254,7 @@ script.execute = function execute(s, stack, tx, index) { case 'ifdup': { if (stack.length === 0) return false; - if (new bn(stack[stack.length - 1]).cmp(0) !== 0) + if (new bn(stack[stack.length - 1]).cmpn(0) !== 0) stack.push(new bn(stack[stack.length - 1]).toArray()); break; } @@ -411,14 +412,14 @@ script.execute = function execute(s, stack, tx, index) { n = n.neg(); break; case 'abs': - if (n.cmp(0) < 0) + if (n.cmpn(0) < 0) n = n.neg(); break; case 'not': - n = n.cmp(0) === 0; + n = n.cmpn(0) === 0; break; case 'noteq0': - n = n.cmp(0) !== 0; + n = n.cmpn(0) !== 0; break; default: return false; @@ -466,10 +467,10 @@ script.execute = function execute(s, stack, tx, index) { n = n1.sub(n2); break; case 'booland': - n = n1.cmp(0) !== 0 && n2.cmp(0) !== 0; + n = n1.cmpn(0) !== 0 && n2.cmpn(0) !== 0; break; case 'boolor': - n = n1.cmp(0) !== 0 || n2.cmp(0) !== 0; + n = n1.cmpn(0) !== 0 || n2.cmpn(0) !== 0; break; case 'numeq': n = n1.cmp(n2) === 0; @@ -501,7 +502,7 @@ script.execute = function execute(s, stack, tx, index) { default: return false; } - var res = n.cmp(0) !== 0; + var res = n.cmpn(0) !== 0; if (o === 'numeqverify') { if (!res) return false; @@ -517,7 +518,7 @@ script.execute = function execute(s, stack, tx, index) { var n2 = new bn(stack.pop()); var n1 = new bn(stack.pop()); var val = n2.cmp(n1) <= 0 && n1.cmp(n3) < 0; - stack.push(val.cmp(0) !== 0 ? [ 1 ] : []); + stack.push(val.cmpn(0) !== 0 ? [ 1 ] : []); break; } @@ -665,7 +666,7 @@ script.execute = function execute(s, stack, tx, index) { // input: [[], sig1, sig2, 1] // prev_out: [[lock], 'checklocktimeverify', 'drop', // 'dup', 'hash160', pubkey, 'equalverify', 'checksig'] - if (stack.length === 0) + if (!tx || stack.length === 0) return false; var lock = new bn(stack[stack.length - 1]).toNumber(); @@ -684,7 +685,7 @@ script.execute = function execute(s, stack, tx, index) { if (lock > tx.lock) return false; - if (input.seq === 0xffffffff) + if (!tx.inputs[index] || tx.inputs[index].seq === 0xffffffff) return false; break; diff --git a/test/script-test.js b/test/script-test.js index 3b5e24ea..e3a91f8e 100644 --- a/test/script-test.js +++ b/test/script-test.js @@ -44,4 +44,46 @@ describe('Script', function() { var decoded = bcoin.script.decode(encoded); assert(bcoin.script.isNullData(decoded)) }) + + it('should handle if statements correctly', function () { + var inputScript = [1, 2]; + var prevOutScript = [2, 'eq', 'if_', 3, 'else_', 4, 'endif', 5]; + var stack = []; + bcoin.script.execute(inputScript, stack); + var res = bcoin.script.execute(prevOutScript, stack); + assert(res); + assert.deepEqual(stack.slice(), [[1], [3], [5]]); + + var inputScript = [1, 2]; + var prevOutScript = [9, 'eq', 'if_', 3, 'else_', 4, 'endif', 5]; + var stack = []; + bcoin.script.execute(inputScript, stack); + var res = bcoin.script.execute(prevOutScript, stack); + assert(res); + assert.deepEqual(stack.slice(), [[1], [4], [5]]); + + var inputScript = [1, 2]; + var prevOutScript = [2, 'eq', 'if_', 3, 'endif', 5]; + var stack = []; + bcoin.script.execute(inputScript, stack); + var res = bcoin.script.execute(prevOutScript, stack); + assert(res); + assert.deepEqual(stack.slice(), [[1], [3], [5]]); + + var inputScript = [1, 2]; + var prevOutScript = [9, 'eq', 'if_', 3, 'endif', 5]; + var stack = []; + bcoin.script.execute(inputScript, stack); + var res = bcoin.script.execute(prevOutScript, stack); + assert(res); + assert.deepEqual(stack.slice(), [[1], [5]]); + + var inputScript = [1, 2]; + var prevOutScript = [9, 'eq', 'notif', 3, 'endif', 5]; + var stack = []; + bcoin.script.execute(inputScript, stack); + var res = bcoin.script.execute(prevOutScript, stack); + assert(res); + assert.deepEqual(stack.slice(), [[1], [3], [5]]); + }) }); From f9186d785aac71b0fe9e771f0e6a05872ed17eda Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 9 Dec 2015 14:35:47 -0800 Subject: [PATCH 56/59] ensure p2sh inputs have only push ops (do the same for raw multisig?). --- lib/bcoin/tx.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index f0149c61..86c5eb68 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -432,8 +432,17 @@ TX.prototype.verify = function verify(index, force) { assert(input.out.tx.outputs.length > input.out.index); var stack = []; - bcoin.script.execute(input.script, stack, this, i); var prev = input.out.tx.outputs[input.out.index].script; + + if (bcoin.script.isScripthash(prev)) { + // p2sh transactions cannot have anything + // other than pushdata ops in the scriptSig + var push = !input.script.slice(1).every(Array.isArray); + if (push) + return false; + } + + bcoin.script.execute(input.script, stack, this, i); var res = bcoin.script.execute(prev, stack, this, i); if (!res) return false; From 26035c3b41f2c4ae5c67b19eba7d9ef8bca3c171 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 9 Dec 2015 14:40:40 -0800 Subject: [PATCH 57/59] minor fix for disabled op_eval. --- lib/bcoin/script.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index e8c45f58..6f9ed62d 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -698,8 +698,7 @@ script.execute = function execute(s, stack, tx, index) { // var res = script.execute(evalScript, stack, tx, index); // if (!res) // return false; - // break; - return false; + break; } default: { // Unknown operation From 57491aaadce2a01d32bbf33bdef4260d3f68341d Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 9 Dec 2015 16:12:52 -0800 Subject: [PATCH 58/59] add network.js and testnet support. see #40. --- lib/bcoin.js | 2 + lib/bcoin/chain.js | 8 +- lib/bcoin/hd.js | 16 ++- lib/bcoin/protocol/constants.js | 23 ----- lib/bcoin/protocol/framer.js | 3 +- lib/bcoin/protocol/index.js | 2 +- lib/bcoin/protocol/network.js | 178 ++++++++++++++++++++++++++++++++ lib/bcoin/protocol/parser.js | 3 +- lib/bcoin/utils.js | 40 ++++++- lib/bcoin/wallet.js | 11 +- 10 files changed, 242 insertions(+), 44 deletions(-) create mode 100644 lib/bcoin/protocol/network.js diff --git a/lib/bcoin.js b/lib/bcoin.js index a638a1af..c80cb2e4 100644 --- a/lib/bcoin.js +++ b/lib/bcoin.js @@ -14,3 +14,5 @@ bcoin.wallet = require('./bcoin/wallet'); bcoin.peer = require('./bcoin/peer'); bcoin.pool = require('./bcoin/pool'); bcoin.hd = require('./bcoin/hd'); + +bcoin.protocol.network.set(process.env.BCOIN_NETWORK || 'main'); diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index 025adeeb..c0937331 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -3,7 +3,7 @@ var EventEmitter = require('events').EventEmitter; var bcoin = require('../bcoin'); var constants = bcoin.protocol.constants; -var preload = bcoin.protocol.preload; +var network = bcoin.protocol.network; var utils = bcoin.utils; var assert = utils.assert; @@ -38,6 +38,8 @@ function Chain(options) { }; this.request = new utils.RequestCache(); + var preload = network.preload; + // Start from the genesis block // if we're a full node. if (this.options.fullNode) { @@ -516,6 +518,7 @@ Chain.prototype.toJSON = function toJSON() { return { v: 1, type: 'chain', + network: network.type, hashes: first.hashes.concat(last.hashes), ts: first.ts.concat(last.ts), heights: first.heights.concat(last.heights) @@ -525,6 +528,7 @@ Chain.prototype.toJSON = function toJSON() { Chain.prototype.fromJSON = function fromJSON(json) { assert.equal(json.v, 1); assert.equal(json.type, 'chain'); + assert.equal(json.network, network.type); this.index.hashes = json.hashes.slice(); this.index.ts = json.ts.slice(); this.index.heights = json.heights.slice(); @@ -534,7 +538,7 @@ Chain.prototype.fromJSON = function fromJSON(json) { this.index.bloom = new bcoin.bloom(28 * 1024 * 1024, 16, 0xdeadbeef); if (this.index.hashes.length === 0) - this.add(new bcoin.block(constants.genesis, 'block')); + this.add(new bcoin.block(network.genesis, 'block')); for (var i = 0; i < this.index.hashes.length; i++) { this.index.bloom.add(this.index.hashes[i], 'hex'); diff --git a/lib/bcoin/hd.js b/lib/bcoin/hd.js index 6bd63298..4297e55e 100644 --- a/lib/bcoin/hd.js +++ b/lib/bcoin/hd.js @@ -56,6 +56,7 @@ var bn = require('bn.js'); var elliptic = require('elliptic'); var utils = bcoin.utils; var assert = utils.assert; +var network = bcoin.protocol.network; var EventEmitter = require('events').EventEmitter; @@ -112,11 +113,6 @@ HDSeed._mnemonic = function(entropy) { * HD Keys */ -var VERSION = { - xpubkey: 0x0488b21e, - xprivkey: 0x0488ade4 -}; - var HARDENED = 0x80000000; var MAX_INDEX = 2 * HARDENED; var MIN_ENTROPY = 128 / 8; @@ -155,7 +151,7 @@ function HDPriv(options) { data = options; } - data = this._normalize(data, VERSION.xprivkey); + data = this._normalize(data, network.prefixes.xprivkey); this.data = data; @@ -165,7 +161,7 @@ function HDPriv(options) { HDPriv.prototype._normalize = function(data, version) { var b; - data.version = version || VERSION.xprivkey; + data.version = version || network.prefixes.xprivkey; data.version = +data.version; data.depth = +data.depth; @@ -219,7 +215,7 @@ HDPriv.prototype._seed = function(seed) { var hash = sha512hmac(seed, 'Bitcoin seed'); return { - // version: VERSION.xprivkey, + // version: network.prefixes.xprivkey, depth: 0, parentFingerPrint: 0, childIndex: 0, @@ -413,7 +409,7 @@ function HDPub(options) { else data = options; - data = this._normalize(data, VERSION.xpubkey); + data = this._normalize(data, network.prefixes.xpubkey); this.data = data; @@ -518,7 +514,7 @@ HDPub.prototype.derive = function(index, hard) { var publicKey = bcoin.ecdsa.keyFromPublic(pubkeyPoint).getPublic(true, 'array'); return new HDPub({ - // version: VERSION.xpubkey, + // version: network.prefixes.xpubkey, depth: this.depth + 1, parentFingerPrint: this.fingerPrint, childIndex: index, diff --git a/lib/bcoin/protocol/constants.js b/lib/bcoin/protocol/constants.js index 25075f09..7f9722c1 100644 --- a/lib/bcoin/protocol/constants.js +++ b/lib/bcoin/protocol/constants.js @@ -3,29 +3,6 @@ var utils = bcoin.utils; exports.minVersion = 70001; exports.version = 70002; -exports.magic = 0xd9b4bef9; -exports.genesis = { - version: 1, - prevBlock: [ 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 ], - merkleRoot: utils.toArray( - '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b', - 'hex' - ).reverse(), - ts: 1231006505, - bits: 0x1d00ffff, - nonce: 2083236893 -}; - -// address versions -exports.addr = { - normal: 0, - p2pkh: 0, - multisig: 0, - p2sh: 5 -}; // version - services field exports.services = { diff --git a/lib/bcoin/protocol/framer.js b/lib/bcoin/protocol/framer.js index 7dfbb213..12c4d0b7 100644 --- a/lib/bcoin/protocol/framer.js +++ b/lib/bcoin/protocol/framer.js @@ -1,4 +1,5 @@ var bcoin = require('../../bcoin'); +var network = require('./network'); var constants = require('./constants'); var utils = bcoin.utils; var assert = utils.assert; @@ -27,7 +28,7 @@ Framer.prototype.header = function header(cmd, payload) { var h = new Array(24); // Magic value - writeU32(h, constants.magic, 0); + writeU32(h, network.magic, 0); // Command var len = writeAscii(h, cmd, 4); diff --git a/lib/bcoin/protocol/index.js b/lib/bcoin/protocol/index.js index 7002c0fb..954909bd 100644 --- a/lib/bcoin/protocol/index.js +++ b/lib/bcoin/protocol/index.js @@ -3,4 +3,4 @@ var protocol = exports; protocol.constants = require('./constants'); protocol.framer = require('./framer'); protocol.parser = require('./parser'); -protocol.preload = require('./preload'); +protocol.network = require('./network'); diff --git a/lib/bcoin/protocol/network.js b/lib/bcoin/protocol/network.js new file mode 100644 index 00000000..1ac88836 --- /dev/null +++ b/lib/bcoin/protocol/network.js @@ -0,0 +1,178 @@ +var bcoin = require('../../bcoin'); +var utils = bcoin.utils; + +/** + * Network + */ + +var network = exports; + +network.set = function(type) { + var net = network[type]; + utils.merge(network, net); +}; + +/** + * Main + */ + +var main = network.main = {}; + +main.prefixes = { + pubkey: 0, + script: 5, + privkey: 128, + xpubkey: 0x0488b21e, + xprivkey: 0x0488ade4 +}; + +utils.merge(main.prefixes, { + normal: main.prefixes.pubkey, + p2pkh: main.prefixes.pubkey, + multisig: main.prefixes.pubkey, + p2sh: main.prefixes.script +}); + +main.type = 'main'; + +main.seeds = [ + 'seed.bitcoin.sipa.be', // Pieter Wuille + 'dnsseed.bluematt.me', // Matt Corallo + 'dnsseed.bitcoin.dashjr.org', // Luke Dashjr + 'seed.bitcoinstats.com', // Christian Decker + 'bitseed.xf2.org', // Jeff Garzik + 'seed.bitcoin.jonasschnelli.ch' // Jonas Schnelli +]; + +main.port = 8333; + +main.alertKey = utils.toArray('' + + '04fc9702847840aaf195de8442ebecedf5b095c' + + 'dbb9bc716bda9110971b28a49e0ead8564ff0db' + + '22209e0374782c093bb899692d524e9d6a6956e' + + '7c5ecbcd68284', + 'hex'); + +main.checkpoints = [ + { height: 11111, hash: '0000000069e244f73d78e8fd29ba2fd2ed618bd6fa2ee92559f542fdb26e7c1d' }, + { height: 33333, hash: '000000002dd5588a74784eaa7ab0507a18ad16a236e7b1ce69f00d7ddfb5d0a6' }, + { height: 74000, hash: '0000000000573993a3c9e41ce34471c079dcf5f52a0e824a81e7f953b8661a20' }, + { height: 105000, hash: '00000000000291ce28027faea320c8d2b054b2e0fe44a773f3eefb151d6bdc97' }, + { height: 134444, hash: '00000000000005b12ffd4cd315cd34ffd4a594f430ac814c91184a0d42d2b0fe' }, + { height: 168000, hash: '000000000000099e61ea72015e79632f216fe6cb33d7899acb35b75c8303b763' }, + { height: 193000, hash: '000000000000059f452a5f7340de6682a977387c17010ff6e6c3bd83ca8b1317' }, + { height: 210000, hash: '000000000000048b95347e83192f69cf0366076336c639f9b7228e9ba171342e' }, + { height: 216116, hash: '00000000000001b4f4b433e81ee46494af945cf96014816a4e2370f11b23df4e' }, + { height: 225430, hash: '00000000000001c108384350f74090433e7fcf79a606b8e797f065b130575932' }, + { height: 250000, hash: '000000000000003887df1f29024b06fc2200b55f8af8f35453d7be294df2d214' }, + { height: 279000, hash: '0000000000000001ae8c72a0b0c301f67e3afca10e819efa9041e458e9bd7e40' }, + { height: 295000, hash: '00000000000000004d9b4ef50f0f9d686fd69db2e03af35a100370c64632a983' } +]; + +main.checkpoints.tsLastCheckpoint = 1397080064; +main.checkpoints.txsLastCheckpoint = 36544669; +main.checkpoints.txsPerDay = 60000.0; + +// http://blockexplorer.com/b/0 +// http://blockexplorer.com/rawblock/000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f +main.genesis = { + version: 1, + _hash: utils.toArray( + '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f', + 'hex' + ).reverse(), + prevBlock: [ 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 ], + merkleRoot: utils.toArray( + '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b', + 'hex' + ).reverse(), + ts: 1231006505, + bits: 0x1d00ffff, + nonce: 2083236893 +}; + +main.magic = 0xd9b4bef9; + +main.preload = require('./preload'); + +/** + * Testnet (v3) + * https://en.bitcoin.it/wiki/Testnet + */ + +var testnet = network.testnet = {}; + +testnet.type = 'testnet'; + +testnet.prefixes = { + pubkey: 111, + script: 196, + privkey: 239, + xpubkey: 0x043587cf, + xprivkey: 0x04358394 +}; + +utils.merge(testnet.prefixes, { + normal: testnet.prefixes.pubkey, + p2pkh: testnet.prefixes.pubkey, + multisig: testnet.prefixes.pubkey, + p2sh: testnet.prefixes.script +}); + +testnet.seeds = [ + 'testnet-seed.alexykot.me', + 'testnet-seed.bitcoin.petertodd.org', + 'testnet-seed.bluematt.me', + 'testnet-seed.bitcoin.schildbach.de' +]; + +testnet.port = 18333; + +testnet.alertKey = utils.toArray('' + + '04302390343f91cc401d56d68b123028bf52e5f' + + 'ca1939df127f63c6467cdf9c8e2c14b61104cf8' + + '17d0b780da337893ecc4aaff1309e536162dabb' + + 'db45200ca2b0a', + 'hex'); + +testnet.checkpoints = [ + { height: 546, hash: '000000002a936ca763904c3c35fce2f3556c559c0214345d31b1bcebf76acb70' } +]; + +testnet.checkpoints.tsLastCheckpoint = 1338180505; +testnet.checkpoints.txsLastCheckpoint = 16341; +testnet.checkpoints.txsPerDay = 300; + +// http://blockexplorer.com/testnet/b/0 +// http://blockexplorer.com/testnet/rawblock/000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943 +testnet.genesis = { + version: 1, + _hash: utils.toArray( + '000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943', + 'hex' + ).reverse(), + prevBlock: [ 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 ], + merkleRoot: utils.toArray( + '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b', + 'hex' + ).reverse(), + ts: 1296688602, + bits: 0x1d00ffff, + nonce: 414098458 +}; + +testnet.magic = 0x0709110b; + +testnet.preload = { + 'v': 1, + 'type': 'chain', + 'hashes': [utils.toHex(testnet.genesis._hash)], + 'ts': [testnet.genesis.ts], + 'heights': [0] +}; diff --git a/lib/bcoin/protocol/parser.js b/lib/bcoin/protocol/parser.js index a5fd849d..c13bcbd9 100644 --- a/lib/bcoin/protocol/parser.js +++ b/lib/bcoin/protocol/parser.js @@ -6,6 +6,7 @@ var bcoin = require('../../bcoin'); var utils = bcoin.utils; var assert = utils.assert; var constants = require('./constants'); +var network = require('./network'); var readU32 = utils.readU32; var readU64 = utils.readU64; @@ -71,7 +72,7 @@ Parser.prototype.parse = function parse(chunk) { Parser.prototype.parseHeader = function parseHeader(h) { var magic = readU32(h, 0); - if (magic !== constants.magic) { + if (magic !== network.magic) { return this._error('Invalid magic value: ' + magic.toString(16)); } diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index d4566c6e..a58e0acd 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -2,6 +2,7 @@ var utils = exports; var bn = require('bn.js'); var hash = require('hash.js'); +var util = require('util'); function toArray(msg, enc) { if (Array.isArray(msg)) @@ -491,6 +492,41 @@ utils.toKeyArray = function(msg) { return utils.fromBase58(msg); }; -utils.debug = function(msg) { - console.log('\x1b[31m' + msg + '\x1b[m'); +utils.inspect = function(obj) { + return typeof obj !== 'string' + ? util.inspect(obj, null, 20, true) + : obj; +}; + +utils.print = function(msg) { + return typeof msg === 'object' + ? process.stdout.write(utils.inspect(msg) + '\n') + : console.log.apply(console, arguments); +}; + +utils.debug = function() { + var args = Array.prototype.slice.call(arguments); + args[0] = '\x1b[31m' + args[0] + '\x1b[m'; + return utils.print.apply(null, args); +}; + +utils.merge = function(target) { + var args = Array.prototype.slice.call(arguments, 1); + args.forEach(function(obj) { + Object.keys(obj).forEach(function(key) { + target[key] = obj[key]; + }); + }); + return target; +}; + +utils.fromBTC = function(btc) { + var satoshi = new bn(+btc || 0); + satoshi.imuln(100000000); + return satoshi; +}; + +utils.ntoBTC = function(satoshi) { + satoshi = new bn(Math.floor(+satoshi || 0).toString(16), 16); + return bcoin.utils.toBTC(satoshi); }; diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 6dfd9a33..4b21ba97 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -6,6 +6,7 @@ var EventEmitter = require('events').EventEmitter; var utils = bcoin.utils; var assert = utils.assert; var constants = bcoin.protocol.constants; +var network = bcoin.protocol.network; function Wallet(options, passphrase) { if (!(this instanceof Wallet)) @@ -166,7 +167,7 @@ Wallet.prototype.getPrivateKey = function getPrivateKey(enc) { if (enc === 'base58') { // We'll be using ncompressed public key as an address - var arr = [ 128 ]; + var arr = [ network.prefixes.privkey ]; // 0-pad key while (arr.length + priv.length < 33) @@ -258,7 +259,7 @@ Wallet.prototype.getAddress = function getAddress() { Wallet.hash2addr = function hash2addr(hash, version) { hash = utils.toArray(hash, 'hex'); - version = constants.addr[version || 'normal']; + version = network.prefixes[version || 'normal']; hash = [ version ].concat(hash); var addr = hash.concat(utils.checksum(hash)); @@ -269,7 +270,7 @@ Wallet.addr2hash = function addr2hash(addr, version) { if (!Array.isArray(addr)) addr = utils.fromBase58(addr); - version = constants.addr[version || 'normal']; + version = network.prefixes[version || 'normal']; if (addr.length !== 25) return []; @@ -417,6 +418,7 @@ Wallet.prototype.toJSON = function toJSON() { return { v: 1, type: 'wallet', + network: network.type, pub: this.getOwnPublicKey('base58'), priv: this.getPrivateKey('base58'), tx: this.tx.toJSON(), @@ -432,6 +434,7 @@ Wallet.prototype.toJSON = function toJSON() { Wallet.fromJSON = function fromJSON(json) { assert.equal(json.v, 1); assert.equal(json.type, 'wallet'); + assert.equal(json.network, network.type); var priv; var pub; @@ -440,7 +443,7 @@ Wallet.fromJSON = function fromJSON(json) { if (json.priv) { var key = bcoin.utils.fromBase58(json.priv); assert(utils.isEqual(key.slice(-4), utils.checksum(key.slice(0, -4)))); - assert.equal(key[0], 128); + assert.equal(key[0], network.prefixes.privkey); key = key.slice(0, -4); if (key.length === 34) { From ef1244442f0f6734e3f11ff4dbb1182b47cc5b60 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 9 Dec 2015 16:32:01 -0800 Subject: [PATCH 59/59] check for network on fromJSON. --- lib/bcoin/chain.js | 3 ++- lib/bcoin/wallet.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index c0937331..308f6925 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -528,7 +528,8 @@ Chain.prototype.toJSON = function toJSON() { Chain.prototype.fromJSON = function fromJSON(json) { assert.equal(json.v, 1); assert.equal(json.type, 'chain'); - assert.equal(json.network, network.type); + if (json.network) + assert.equal(json.network, network.type); this.index.hashes = json.hashes.slice(); this.index.ts = json.ts.slice(); this.index.heights = json.heights.slice(); diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 4b21ba97..dc4864aa 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -434,7 +434,8 @@ Wallet.prototype.toJSON = function toJSON() { Wallet.fromJSON = function fromJSON(json) { assert.equal(json.v, 1); assert.equal(json.type, 'wallet'); - assert.equal(json.network, network.type); + if (json.network) + assert.equal(json.network, network.type); var priv; var pub;