diff --git a/lib/bcoin/keyring.js b/lib/bcoin/keyring.js index 7f450ea8..a2a88e0b 100644 --- a/lib/bcoin/keyring.js +++ b/lib/bcoin/keyring.js @@ -14,8 +14,6 @@ var assert = utils.assert; * @exports KeyRing * @constructor * @param {Object} options - * @param {String?} options.label - * @param {Boolean?} options.derived * @param {HDPrivateKey|HDPublicKey} options.key * @param {String?} options.path * @param {Boolean?} options.change @@ -40,10 +38,9 @@ function KeyRing(options) { options = {}; this.options = options; - this.label = options.label || ''; - this.derived = !!options.derived; this.addressMap = null; + this.network = bcoin.network.get(options.network); this.key = options.key; this.path = options.path; this.change = !!options.change; @@ -63,7 +60,7 @@ function KeyRing(options) { if (this.m < 1 || this.m > this.n) throw new Error('m ranges between 1 and n'); - this.addKey(this.getPublicKey()); + this.addKey(this.key); if (options.keys) { for (i = 0; i < options.keys.length; i++) @@ -129,7 +126,13 @@ KeyRing.prototype.removeKey = function removeKey(key) { */ KeyRing.prototype.getPublicKey = function getPublicKey(enc) { - return this.key.getPublicKey(enc); + if (enc === 'base58') + return utils.toBase58(this.key); + + if (enc === 'hex') + return this.key.toString('hex'); + + return this.key; }; /** @@ -455,6 +458,7 @@ KeyRing.prototype.scriptInputs = function scriptInputs(tx, index) { * Build input scripts and sign inputs for a transaction. Only attempts * to build/sign inputs that are redeemable by this address. * @param {MTX} tx + * @param {HDPrivateKey|KeyPair|Buffer} key - Private key. * @param {Number?} index - Index of input. If not present, * it will attempt to build and sign all redeemable inputs. * @param {SighashType?} type @@ -539,6 +543,54 @@ KeyRing.prototype.__defineGetter__('address', function() { return this.getAddress(); }); +/** + * Convert an KeyRing to a more json-friendly object. + * @param {String?} passphrase - KeyRing passphrase + * @returns {Object} + */ + +KeyRing.prototype.toJSON = function toJSON() { + return { + v: 1, + name: 'address', + address: this.getAddress(), + network: this.network.type, + change: this.change, + index: this.index, + path: this.path, + key: utils.toBase58(this.key), + type: this.type, + witness: this.witness, + keys: this.keys.map(utils.toBase58), + m: this.m, + n: this.n + }; +}; + +/** + * Instantiate an KeyRing from a jsonified transaction object. + * @param {Object} json - The jsonified transaction object. + * @param {String?} passphrase - KeyRing passphrase + * @returns {KeyRing} + */ + +KeyRing.fromJSON = function fromJSON(json) { + assert.equal(json.v, 1); + assert.equal(json.name, 'address'); + return new KeyRing({ + nework: json.network, + change: json.change, + index: json.index, + path: json.path, + key: utils.fromBase58(json.key), + type: json.type, + witness: json.witness, + keys: json.keys.map(utils.fromBase58), + m: json.m, + n: json.n + }); +}; + /* * Expose */ diff --git a/lib/bcoin/mtx.js b/lib/bcoin/mtx.js index 6fb036d2..b8edf49c 100644 --- a/lib/bcoin/mtx.js +++ b/lib/bcoin/mtx.js @@ -353,8 +353,9 @@ MTX.prototype.createSignature = function createSignature(index, prev, key, type, /** * Sign an input. * @param {Number} index - Index of input being signed. - * @param {Address} addr - Address used to sign. The address + * @param {KeyRing} addr - Address used to sign. The address * must be able to redeem the coin. + * @param {HDPrivateKey|KeyPair|Buffer} key - Private key. * @param {SighashType} type * @returns {Boolean} Whether the input was able to be signed. * @throws on unavailable coins. @@ -623,8 +624,9 @@ MTX.prototype.isSigned = function isSigned() { /** * Built input scripts (or witnesses) and sign the inputs. * @param {Number} index - Index of input being signed. - * @param {Address} addr - Address used to sign. The address + * @param {KeyRing} addr - Address used to sign. The address * must be able to redeem the coin. + * @param {HDPrivateKey|KeyPair|Buffer} key - Private key. * @param {SighashType} type * @returns {Boolean} Whether the input was able to be signed. * @throws on unavailable coins. @@ -656,7 +658,7 @@ MTX.prototype.sign = function sign(index, addr, key, type) { * tx.addOutput({ address: ..., value: new bn(100000) }); * tx.addOutput({ address: ..., value: utils.satoshi('0.1') }); * tx.addOutput(receivingWallet, utils.satoshi('0.1')); - * @param {Wallet|Address|Object} obj - Wallet, Address, + * @param {Wallet|KeyRing|Object} obj - Wallet, Address, * or options (see {@link Script.createOutputScript} for options). * @param {Amount?} value - Only needs to be present for non-options. */ diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 73edeb69..7733e560 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -83,6 +83,7 @@ function Wallet(options) { this.copayBIP45 = options.copayBIP45 || false; this.lookahead = options.lookahead != null ? options.lookahead : 5; this.cosignerIndex = -1; + this.initialized = false; this.type = options.type || 'pubkeyhash'; this.derivation = options.derivation || 'bip44'; @@ -154,8 +155,9 @@ Wallet.prototype._init = function _init() { var self = this; var i; - assert(!this._initialized); - this._initialized = true; + assert(!this.initialized); + + this.initialized = true; if (Object.keys(this.addressMap).length === 0) { for (i = 0; i < this.receiveDepth - 1; i++) @@ -178,15 +180,32 @@ Wallet.prototype._init = function _init() { assert(!this.receiveAddress.change); assert(this.changeAddress.change); - if (!this.provider) - return; - - this.provider.setID(this.id); - this.on('error', function(err) { bcoin.debug('Wallet Error: %s', err.message); }); + this.setProvider(this.provider, function(err) { + if (err) + return self.emit('error', err); + + self.loaded = true; + self.emit('open'); + }); +}; + +Wallet.prototype.setProvider = function setProvider(provider, callback) { + var self = this; + + if (!provider) + return callback(); + + if (this.provider !== provider) + this.provider.destroy(); + + this.provider = provider; + + this.provider.setID(this.id); + this.provider.on('error', function(err) { self.emit('error', err); }); @@ -212,13 +231,7 @@ Wallet.prototype._init = function _init() { self.emit('unconfirmed', tx); }); - this.provider.open(function(err) { - if (err) - return self.emit('error', err); - - self.loaded = true; - self.emit('open'); - }); + this.provider.open(callback); }; /** @@ -392,9 +405,6 @@ Wallet.prototype.getID = function getID() { var publicKey = this.accountKey.publicKey; var p; - if (this.options.id) - return this.options.id; - p = new BufferWriter(); p.writeU8(0x03); p.writeU8(0xbe); @@ -484,9 +494,9 @@ Wallet.prototype.deriveChange = function deriveChange(index) { */ Wallet.prototype.deriveAddress = function deriveAddress(change, index) { - var path, data, key, options, address; + var i, path, data, key, options, address; - assert(this._initialized); + assert(this.initialized); if (typeof change === 'string') path = change; @@ -517,7 +527,7 @@ Wallet.prototype.deriveAddress = function deriveAddress(change, index) { options = { network: this.network, - key: key, + key: key.publicKey, change: data.change, index: data.index, path: data.path, @@ -525,15 +535,15 @@ Wallet.prototype.deriveAddress = function deriveAddress(change, index) { witness: this.witness, m: this.m, n: this.n, - keys: [], - derived: true + keys: [] }; - this.keys.forEach(function(key, cosignerIndex) { - var path = this.createPath(cosignerIndex, data.change, data.index); + for (i = 0; i < this.keys.length; i++) { + key = this.keys[i]; + path = this.createPath(i, data.change, data.index); key = key.derive(path); options.keys.push(key.publicKey); - }, this); + } address = new bcoin.keyring(options); @@ -724,7 +734,7 @@ Wallet.prototype.fill = function fill(tx, options, callback) { if (!options) options = {}; - assert(this._initialized); + assert(this.initialized); this.getCoins(function(err, coins) { if (err) @@ -858,6 +868,9 @@ Wallet.prototype.createTX = function createTX(options, outputs, callback) { if (!tx.checkInputs(height)) return callback(new Error('CheckInputs failed.')); + if (!self.scriptInputs(tx)) + return callback(new Error('scriptInputs failed.')); + return callback(null, tx); }); }; @@ -1095,7 +1108,7 @@ Wallet.prototype._scan = function _scan(getByAddress, callback) { var depth = { changeDepth: 0, receiveDepth: 0 }; var all = []; - assert(this._initialized); + assert(this.initialized); (function chainCheck(change) { var addressIndex = 0; @@ -1501,13 +1514,13 @@ Wallet.prototype.inspect = function inspect() { network: this.network.type, m: this.m, n: this.n, - keyAddress: this._initialized + keyAddress: this.initialized ? this.keyAddress : null, - scriptAddress: this._initialized + scriptAddress: this.initialized ? this.scriptAddress : null, - programAddress: this._initialized + programAddress: this.initialized ? this.programAddress : null, witness: this.witness, @@ -1653,7 +1666,6 @@ MasterKey.fromJSON = function fromJSON(json) { }); }; - /* * Expose */ diff --git a/lib/bcoin/walletdb.js b/lib/bcoin/walletdb.js index 5db57a32..f895c53c 100644 --- a/lib/bcoin/walletdb.js +++ b/lib/bcoin/walletdb.js @@ -40,6 +40,7 @@ function WalletDB(options) { EventEmitter.call(this); + this.providers = []; this.options = options; this.loaded = false; this.network = bcoin.network.get(options.network); @@ -136,14 +137,14 @@ WalletDB.prototype._init = function _init() { this.tx.on('tx', function(tx, map) { self.emit('tx', tx, map); map.all.forEach(function(id) { - self.emit(id + ' tx', tx); + self.fire(id, 'tx', tx); }); }); this.tx.on('confirmed', function(tx, map) { self.emit('confirmed', tx, map); map.all.forEach(function(id) { - self.emit(id + ' confirmed', tx); + self.fire(id, 'confirmed', tx); }); utils.forEachSerial(map.output, function(id, next) { self.syncOutputDepth(id, tx, next); @@ -156,7 +157,7 @@ WalletDB.prototype._init = function _init() { this.tx.on('unconfirmed', function(tx, map) { self.emit('unconfirmed', tx, map); map.all.forEach(function(id) { - self.emit(id + ' unconfirmed', tx); + self.fire(id, 'unconfirmed', tx); }); }); @@ -165,12 +166,12 @@ WalletDB.prototype._init = function _init() { self.emit('updated', tx, map); map.all.forEach(function(id) { - self.emit(id + ' updated', tx); + self.fire(id, 'updated', tx); }); utils.forEachSerial(map.output, function(id, next) { if (self.listeners('balance').length === 0 - && self.listeners(id + ' balance').length === 0) { + && !self.hasListener(id, ' balance')) { return next(); } @@ -181,7 +182,7 @@ WalletDB.prototype._init = function _init() { balances[id] = balance; self.emit('balance', balance, id); - self.emit(id + ' balance', balance); + self.fire(id, 'balance', balance); next(); }); @@ -447,28 +448,38 @@ WalletDB.prototype.get = function get(id, callback) { /** * Save a wallet to the database (setup ida and encrypt). - * @param {WalletID?} id - * @param {Wallet} options + * @param {Wallet} wallet * @param {Function} callback */ WalletDB.prototype.save = function save(wallet, callback) { + var self = this; + if (Array.isArray(wallet)) { + return utils.forEachSerial(wallet, function(wallet, next) { + self.save(wallet, next); + }, callback); + } this.saveJSON(wallet.id, wallet.toJSON(), callback); }; /** * Remove wallet from the database. Destroy wallet if passed in. - * @param {WalletID|Wallet} id + * @param {WalletID} id * @param {Function} callback */ WalletDB.prototype.remove = function remove(id, callback) { + var self = this; + if (Array.isArray(wallet)) { + return utils.forEachSerial(id, function(id, next) { + self.remove(id, next); + }, callback); + } return this.removeJSON(id, callback); }; /** * Create a new wallet, save to database, setup provider. - * @param {WalletID?} id * @param {Object} options - See {@link Wallet}. * @param {Function} callback - Returns [Error, {@link Wallet}]. */ @@ -703,38 +714,53 @@ WalletDB.prototype.provider = function provider() { }; WalletDB.prototype.register = function register(id, provider) { - if (!this.listeners[id]) - this.listeners[id] = []; + if (!this.providers[id]) + this.providers[id] = []; - if (this.listeners[id].indexOf(provider) !== -1) - this.listeners[id].push(provider); + if (this.providers[id].indexOf(provider) === -1) + this.providers[id].push(provider); }; WalletDB.prototype.unregister = function unregister(id, provider) { - var listeners = this.listeners[id]; + var providers = this.providers[id]; var i; - if (!listeners) + if (!providers) return; - i = listeners.indexOf(provider); + i = providers.indexOf(provider); if (i !== -1) - listeners.splice(i, 1); + providers.splice(i, 1); - if (listeners.length === 0) - delete this.listeners[id]; + if (providers.length === 0) + delete this.providers[id]; }; WalletDB.prototype.fire = function fire(id) { var args = Array.prototype.slice.call(arguments, 1); - var listeners = this.listeners[id]; + var providers = this.providers[id]; var i; - if (!listeners) + if (!providers) return; - for (i = 0; i < listeners.length; i++) - listeners.emit.apply(listener, args); + for (i = 0; i < providers.length; i++) + providers[i].emit.apply(providers[i], args); +}; + +WalletDB.prototype.hasListener = function hasListener(id, event) { + var providers = this.providers[id]; + var i; + + if (!providers) + return false; + + for (i = 0; i < providers.length; i++) { + if (providers[i].listeners(event).length !== 0) + return true; + } + + return false; }; /** @@ -800,26 +826,7 @@ Provider.prototype.setID = function setID(id) { assert(!this.id, 'ID has already been set.'); this.id = id; - - this.db.on(id + ' tx', this._onTX = function(tx) { - self.emit('tx', tx); - }); - - this.db.on(id + ' updated', this._onUpdated = function(tx) { - self.emit('updated', tx); - }); - - this.db.on(id + ' confirmed', this._onConfirmed = function(tx) { - self.emit('confirmed', tx); - }); - - this.db.on(id + ' unconfirmed', this._onUnconfirmed = function(tx) { - self.emit('unconfirmed', tx); - }); - - this.db.on(id + ' balance', this._onBalance = function(balance) { - self.emit('balance', balance); - }); + this.db.register(this.id, this); }; /** @@ -832,35 +839,12 @@ Provider.prototype.close = Provider.prototype.destroy = function destroy(callback) { callback = utils.ensure(callback); - if (!this.db) + if (!this.id) return utils.nextTick(callback); - if (this._onTX) { - this.db.removeListener(this.id + ' tx', this._onTX); - delete this._onTX; - } - - if (this._onUpdated) { - this.db.removeListener(this.id + ' updated', this._onUpdated); - delete this._onUpdated; - } - - if (this._onConfirmed) { - this.db.removeListener(this.id + ' confirmed', this._onConfirmed); - delete this._onConfirmed; - } - - if (this._onUnconfirmed) { - this.db.removeListener(this.id + ' unconfirmed', this._onUnconfirmed); - delete this._onUnconfirmed; - } - - if (this._onBalance) { - this.db.removeListener(this.id + ' balance', this._onBalance); - delete this._onBalance; - } - + this.db.unregister(this.id, this); this.db = null; + this.id = null; return utils.nextTick(callback); }; @@ -999,11 +983,9 @@ Provider.prototype.save = function save(wallet, callback) { }; /** - * Notify the provider backend that a new address was - * derived (not technically necessary if you're - * implementing a provider). - * @param {Wallet} wallet - * @param {Address} address + * Add a key to the wallet. + * @param {HDPublicKey} key + * @param {Function} callback */ Provider.prototype.addKey = function addKey(key, callback) { @@ -1011,23 +993,9 @@ Provider.prototype.addKey = function addKey(key, callback) { }; /** - * Notify the provider backend that a new address was - * derived (not technically necessary if you're - * implementing a provider). - * @param {Wallet} wallet - * @param {Address} address - */ - -// Provider.prototype.deriveInputs = function deriveInputs(tx, index, callback) { -// return this.db.deriveInputs(this.id, tx, index, callback); -// }; - -/** - * Notify the provider backend that a new address was - * derived (not technically necessary if you're - * implementing a provider). - * @param {Wallet} wallet - * @param {Address} address + * Remove a key from the wallet. + * @param {HDPublicKey} key + * @param {Function} callback */ Provider.prototype.removeKey = function removeKey(key, callback) { @@ -1035,11 +1003,8 @@ Provider.prototype.removeKey = function removeKey(key, callback) { }; /** - * Notify the provider backend that a new address was - * derived (not technically necessary if you're - * implementing a provider). - * @param {Wallet} wallet - * @param {Address} address + * Create a receiving address. + * @param {Function} callback */ Provider.prototype.createReceive = function createReceive(callback) { @@ -1047,11 +1012,8 @@ Provider.prototype.createReceive = function createReceive(callback) { }; /** - * Notify the provider backend that a new address was - * derived (not technically necessary if you're - * implementing a provider). - * @param {Wallet} wallet - * @param {Address} address + * Create a change address. + * @param {Function} callback */ Provider.prototype.createChange = function createChange(callback) {