diff --git a/bin/node b/bin/node index 4917aff5..06b6db92 100755 --- a/bin/node +++ b/bin/node @@ -5,9 +5,10 @@ var utils = bcoin.utils; var assert = utils.assert; var node = bcoin.fullnode({ - debug: true + debug: true, + passphrase: 'node' }); node.on('error', function(err) { - utils.print(err.message); + utils.debug(err.message); }); diff --git a/lib/bcoin/fullnode.js b/lib/bcoin/fullnode.js index 4b17b342..8a36e943 100644 --- a/lib/bcoin/fullnode.js +++ b/lib/bcoin/fullnode.js @@ -4,7 +4,6 @@ * https://github.com/indutny/bcoin */ -var EventEmitter = require('events').EventEmitter; var bcoin = require('../bcoin'); var bn = require('bn.js'); var constants = bcoin.protocol.constants; @@ -21,8 +20,6 @@ function Fullnode(options) { if (!(this instanceof Fullnode)) return new Fullnode(options); - EventEmitter.call(this); - if (!options) options = {}; @@ -40,6 +37,7 @@ utils.inherits(Fullnode, bcoin.node); Fullnode.prototype._init = function _init() { var self = this; var pending = 3; + var options; this.loading = true; @@ -147,8 +145,13 @@ Fullnode.prototype._init = function _init() { } } + options = { + id: 'primary', + passphrase: this.options.passphrase + }; + // Create or load the primary wallet. - this.createWallet({ id: 'primary', passphrase: 'node' }, function(err, wallet) { + this.createWallet(options, function(err, wallet) { if (err) throw err; @@ -193,6 +196,10 @@ Fullnode.prototype.createWallet = function createWallet(options, callback) { }); }; +Fullnode.prototype.getWallet = function getWallet(id, passphrase, callback) { + return this.walletdb.get(id, passphrase, callback); +}; + Fullnode.prototype.scanWallet = function scanWallet(wallet, callback) { wallet.scan(this.getTXByAddress.bind(this), callback); }; diff --git a/lib/bcoin/hd.js b/lib/bcoin/hd.js index d9632ce7..c4f3770f 100644 --- a/lib/bcoin/hd.js +++ b/lib/bcoin/hd.js @@ -70,20 +70,31 @@ function HDSeed(options) { if (!(this instanceof HDSeed)) return new HDSeed(options); - options = options || {}; + if (!options) + options = {}; this.bits = options.bits || 128; - this.entropy = options.entropy || bcoin.ec.random(this.bits / 8); - this.mnemonic = options.mnemonic || HDSeed._mnemonic(this.entropy); - this.seed = this.createSeed(options.passphrase); + this.entropy = options.entropy; + this.mnemonic = options.mnemonic; + this.passphrase = options.passphrase || ''; } HDSeed.prototype.createSeed = function createSeed(passphrase) { - this.passphrase = passphrase || ''; - return pbkdf2(this.mnemonic, 'mnemonic' + this.passphrase, 2048, 64); + if (this.seed) + return this.seed; + + if (!this.entropy) + this.entropy = bcoin.ec.random(this.bits / 8); + + if (!this.mnemonic) + this.mnemonic = this.createMnemonic(this.entropy); + + this.seed = pbkdf2(this.mnemonic, 'mnemonic' + this.passphrase, 2048, 64); + + return this.seed; }; -HDSeed._mnemonic = function _mnemonic(entropy) { +HDSeed.prototype.createMnemonic = function createMnemonic(entropy) { var bin = ''; var mnemonic = []; var i, wi; @@ -143,12 +154,12 @@ function HDPrivateKey(options) { return new HDPublicKey(options); this.network = options.network || network.type; + this.seed = options.seed; - if (options.seed) { - this.seed = options.seed; - data = this._seed(options.seed); - } else if (options.xkey) { + if (options.xkey) { data = this._unbuild(options.xkey); + } else if (options.seed) { + data = this._seed(options.seed); } else { data = options.data; } @@ -549,7 +560,7 @@ HDPrivateKey.prototype._seed = function _seed(seed) { var hash; if (seed instanceof HDSeed) - seed = seed.seed; + seed = seed.createSeed(); if (utils.isHex(seed)) seed = new Buffer(seed, 'hex'); @@ -631,10 +642,13 @@ HDPrivateKey.prototype._unbuild = function _unbuild(xkey) { if (!utils.isEqual(data.checksum, hash)) throw new Error('checksum mismatch'); - if (data.version === network.main.prefixes.xprivkey) - this.network = 'main'; - else - this.network = 'testnet'; + network.types.some(function(type) { + if (utils.readU32BE(data.version) === network[type].prefixes.xprivkey) { + this.network = type; + return true; + } + return false; + }, this); this.xprivkey = xkey; @@ -703,7 +717,7 @@ HDPrivateKey.prototype.derive = function derive(index, hardened) { var off = 0; if (typeof index === 'string') - return this.deriveString(index); + return this.derivePath(index); cached = cache.get(this.xprivkey, index); @@ -808,7 +822,7 @@ HDPrivateKey.isValidPath = function isValidPath(path, hardened) { return false; }; -HDPrivateKey.prototype.deriveString = function deriveString(path) { +HDPrivateKey.prototype.derivePath = function derivePath(path) { var indexes, child; if (!HDPrivateKey.isValidPath(path)) @@ -825,10 +839,11 @@ HDPrivateKey.prototype.toJSON = function toJSON(passphrase) { var json = { v: 1, name: 'hdkey', - encrypted: passphrase ? true : false + encrypted: false }; if (this instanceof HDPrivateKey) { + json.encrypted = passphrase ? true : false; if (this.seed) { json.mnemonic = passphrase ? utils.encrypt(this.seed.mnemonic, passphrase) @@ -836,7 +851,6 @@ HDPrivateKey.prototype.toJSON = function toJSON(passphrase) { json.passphrase = passphrase ? utils.encrypt(this.seed.passphrase, passphrase) : this.seed.passphrase; - return json; } json.xprivkey = passphrase ? utils.encrypt(this.xprivkey, passphrase) @@ -850,6 +864,8 @@ HDPrivateKey.prototype.toJSON = function toJSON(passphrase) { }; HDPrivateKey._fromJSON = function _fromJSON(json, passphrase) { + var data = {}; + assert.equal(json.v, 1); assert.equal(json.name, 'hdkey'); @@ -857,24 +873,23 @@ HDPrivateKey._fromJSON = function _fromJSON(json, passphrase) { throw new Error('Cannot decrypt address'); if (json.mnemonic) { - return { - seed: { - mnemonic: json.encrypted - ? utils.decrypt(json.mnemonic, passphrase) - : json.mnemonic, - passphrase: json.encrypted - ? utils.decrypt(json.passphrase, passphrase) - : json.passphrase - } + data.seed = { + mnemonic: json.encrypted + ? utils.decrypt(json.mnemonic, passphrase) + : json.mnemonic, + passphrase: json.encrypted + ? utils.decrypt(json.passphrase, passphrase) + : json.passphrase }; + if (!json.xprivkey) + return data; } if (json.xprivkey) { - return { - xprivkey: json.encrypted - ? utils.decrypt(json.xprivkey, passphrase) - : json.xprivkey - }; + data.xprivkey = json.encrypted + ? utils.decrypt(json.xprivkey, passphrase) + : json.xprivkey; + return data; } if (json.xpubkey) { @@ -889,15 +904,16 @@ HDPrivateKey._fromJSON = function _fromJSON(json, passphrase) { HDPrivateKey.fromJSON = function fromJSON(json, passphrase) { json = HDPrivateKey._fromJSON(json, passphrase); - if (json.seed) - return HDPrivateKey.fromSeed(json.seed); - if (json.xprivkey) { return new HDPrivateKey({ - xkey: json.xprivkey + xkey: json.xprivkey, + seed: json.seed ? new HDSeed(json.seed) : null }); } + if (json.seed) + return HDPrivateKey.fromSeed(json.seed); + if (json.xpubkey) { return new HDPublicKey({ xkey: json.xpubkey @@ -1008,10 +1024,13 @@ HDPublicKey.prototype._unbuild = function _unbuild(xkey) { if (!utils.isEqual(data.checksum, hash)) throw new Error('checksum mismatch'); - if (data.version === network.main.prefixes.xpubkey) - this.network = 'main'; - else - this.network = 'testnet'; + network.types.some(function(type) { + if (utils.readU32BE(data.version) === network[type].prefixes.xprivkey) { + this.network = type; + return true; + } + return false; + }, this); this.xpubkey = xkey; @@ -1066,7 +1085,7 @@ HDPublicKey.prototype.derive = function derive(index, hardened) { var publicPoint, point, publicKey, child; if (typeof index === 'string') - return this.deriveString(index); + return this.derivePath(index); cached = cache.get(this.xpubkey, index); @@ -1126,7 +1145,7 @@ HDPublicKey.isValidPath = function isValidPath(arg) { return false; }; -HDPublicKey.prototype.deriveString = function deriveString(path) { +HDPublicKey.prototype.derivePath = function derivePath(path) { if (~path.indexOf('\'')) throw new Error('cannot derive hardened'); else if (!HDPublicKey.isValidPath(path)) diff --git a/lib/bcoin/spvnode.js b/lib/bcoin/spvnode.js index 68b87ab1..bc816a02 100644 --- a/lib/bcoin/spvnode.js +++ b/lib/bcoin/spvnode.js @@ -4,7 +4,6 @@ * https://github.com/indutny/bcoin */ -var EventEmitter = require('events').EventEmitter; var bcoin = require('../bcoin'); var bn = require('bn.js'); var constants = bcoin.protocol.constants; @@ -21,22 +20,14 @@ function SPVNode(options) { if (!(this instanceof SPVNode)) return new SPVNode(options); - EventEmitter.call(this); - if (!options) options = {}; - this.options = options; - - if (this.options.debug) - bcoin.debug = this.options.debug; - - if (this.options.network) - network.set(this.options.network); + bcoin.node.call(this, options); this.pool = null; this.chain = null; - this.wallet = null; + this.walletdb = null; this.loading = false; @@ -45,70 +36,88 @@ function SPVNode(options) { this._init(); } -utils.inherits(SPVNode, EventEmitter); +utils.inherits(SPVNode, bcoin.node); SPVNode.prototype._init = function _init() { var self = this; + var options; this.loading = true; - if (!this.options.pool) - this.options.pool = {}; + this.chain = new bcoin.chain(this, { + spv: true, + preload: true, + fsync: false + }); - this.options.pool.spv = true; - this.options.pool.preload = this.options.pool.preload !== false; + this.pool = new bcoin.pool(this, { + witness: this.network.type === 'segnet', + spv: true + }); - this.pool = new bcoin.pool(this.options.pool); - this.chain = this.pool.chain; - - this.walletdb = new bcoin.walletdb(this.options.walletdb); + this.walletdb = new bcoin.walletdb(this); this.pool.on('error', function(err) { self.emit('error', err); }); - this.pool.on('tx', function(tx) { - self.wallet.addTX(tx); + this.chain.on('error', function(err) { + self.emit('error', err); }); - if (!this.options.wallet) - this.options.wallet = {}; + this.walletdb.on('error', function(err) { + self.emit('error', err); + }); - if (!this.options.wallet.id) - this.options.wallet.id = 'primary'; + this.pool.on('tx', function(tx) { + self.walletdb.addTX(tx, function(err) { + if (err) + self.emit('error', err); + }); + }); - if (!this.options.wallet.passphrase) - this.options.wallet.passphrase = 'node'; + options = { + id: 'primary', + passphrase: this.options.passphrase + }; - this.walletdb.create(this.options.wallet, function(err, wallet) { + this.createWallet(options, function(err, wallet) { if (err) throw err; - self.wallet = wallet; + self.loading = false; + self.emit('load'); + self.pool.startSync(); + + utils.debug('Node is loaded and syncing.'); + }); +}; + +SPVNode.prototype.createWallet = function createWallet(options, callback) { + var self = this; + callback = utils.ensure(callback); + this.walletdb.create(options, function(err, wallet) { + if (err) + return callback(err); + + assert(wallet); utils.debug('Loaded wallet with id=%s address=%s', - wallet.getID(), wallet.getAddress()); + wallet.id, wallet.getAddress()); - // Handle forks - self.chain.on('remove entry', function(entry) { - self.wallet.tx.getAll().forEach(function(tx) { - if (tx.block === entry.hash || tx.height >= entry.height) - self.wallet.tx.unconfirm(tx); - }); - }); - - self.pool.addWallet(this.wallet, function(err) { + self.pool.addWallet(wallet, function(err) { if (err) - throw err; + return callback(err); - self.pool.startSync(); - - self.loading = false; - self.emit('load'); + return callback(null, wallet); }); }); }; +SPVNode.prototype.getWallet = function getWallet(id, passphrase, callback) { + return this.walletdb.get(id, passphrase, callback); +}; + /** * Expose */