diff --git a/lib/bcoin/crypto.js b/lib/bcoin/crypto.js index a48add8e..81d39c81 100644 --- a/lib/bcoin/crypto.js +++ b/lib/bcoin/crypto.js @@ -266,13 +266,10 @@ crypto.decrypt = function decrypt(data, passphrase, iv, callback) { try { data = crypto.decipher(data, key, iv); } catch (e) { - key.fill(0); return callback(e); } - key.fill(0); - - return callback(null, data); + return callback(null, data, key); }); }; diff --git a/lib/bcoin/keyring.js b/lib/bcoin/keyring.js index c5fd9e10..0f7aa6a9 100644 --- a/lib/bcoin/keyring.js +++ b/lib/bcoin/keyring.js @@ -326,42 +326,6 @@ KeyRing.fromSecret = function fromSecret(data) { return new KeyRing().fromSecret(data); }; -/** - * Inject properties from account object. - * @private - * @param {Account} account - * @param {Buffer} key - * @param {Buffer[]} keys - * @param {Number} change - * @param {Number} index - */ - -KeyRing.prototype.fromAccount = function fromAccount(account, key, keys, change, index) { - this.network = account.network; - this.publicKey = key.publicKey; - - if (account.n > 1) - this.script = bcoin.script.fromMultisig(account.m, account.n, keys); - - this.witness = account.witness; - - return this; -}; - -/** - * Instantiate key ring from an account. - * @param {Account} account - * @param {Buffer} key - * @param {Buffer[]} keys - * @param {Number} change - * @param {Number} index - * @returns {KeyRing} - */ - -KeyRing.fromAccount = function fromAccount(account, key, keys, change, index) { - return new KeyRing().fromAccount(account, key, keys, change, index); -}; - /** * Get public key. * @param {String?} enc - `"hex"` or `null`. diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 3480be2d..594b8fd2 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -628,46 +628,6 @@ Wallet.prototype.createReceive = function createReceive(account, callback) { return this.createAddress(account, false, callback); }; -Wallet.prototype.importKey = function importKey(account, ring, callback) { - var self = this; - - if (typeof ring === 'function') { - callback = ring; - ring = account; - account = 0; - } - - callback = this._lockWrite(importKey, [account, ring, callback]); - - if (!callback) - return; - - this.getAccount(account, function(err, account) { - if (err) - return callback(err); - - if (!account) - return callback(new Error('Account not found.')); - - self.start(); - - ring.path = bcoin.walletdb.Path.fromAccount(account, ring, -1, -1); - ring.path.imported = ring.toRaw(); - - account.saveAddress([ring], function(err) { - if (err) { - self.drop(); - return callback(err); - } - self.commit(function(err) { - if (err) - return callback(err); - callback(); - }); - }); - }, true); -}; - /** * Create a new change address (increments receiveDepth). * @param {(Number|String)?} account @@ -831,6 +791,71 @@ Wallet.prototype.getPaths = function getPaths(account, callback) { }); }; +/** + * Import a keyring (will not exist on derivation chain). + * Rescanning must be invoked manually. + * @param {(String|Number)?} account + * @param {KeyRing} ring + * @param {(String|Buffer)?} passphrase + * @param {Function} callback + */ + +Wallet.prototype.importKey = function importKey(account, ring, passphrase, callback) { + var self = this; + var raw, path; + + if (typeof passphrase === 'function') { + callback = passphrase; + passphrase = null; + } + + if (typeof ring === 'function') { + callback = ring; + ring = account; + account = 0; + } + + callback = this._lockWrite(importKey, [account, ring, callback]); + + if (!callback) + return; + + this.getAccount(account, function(err, account) { + if (err) + return callback(err); + + if (!account) + return callback(new Error('Account not found.')); + + self.unlock(passphrase, null, function(err) { + if (err) + return callback(err); + + raw = ring.toRaw(); + path = bcoin.path.fromAccount(account, ring); + + if (self.master.encrypted) { + raw = self.master.encipher(raw, path.hash); + assert(raw); + path.encrypted = true; + } + + path.imported = raw; + ring.path = path; + + self.start(); + + account.saveAddress([ring], function(err) { + if (err) { + self.drop(); + return callback(err); + } + self.commit(callback); + }); + }, true); + }); +}; + /** * Fill a transaction with inputs, estimate * transaction size, calculate fee, and add a change output. @@ -1067,16 +1092,11 @@ Wallet.prototype.resend = function resend(callback) { * @param {Function} callback - Returns [Error, {@link KeyRing}[]]. */ -Wallet.prototype.deriveInputs = function deriveInputs(tx, master, callback) { +Wallet.prototype.deriveInputs = function deriveInputs(tx, callback) { var self = this; var rings = []; var ring; - if (typeof master === 'function') { - callback = master; - master = null; - } - this.getInputPaths(tx, function(err, paths) { if (err) return callback(err); @@ -1089,8 +1109,10 @@ Wallet.prototype.deriveInputs = function deriveInputs(tx, master, callback) { if (!account) return next(); - ring = account.derivePath(path, master); - rings.push(ring); + ring = account.derivePath(path, self.master); + + if (ring) + rings.push(ring); next(); }); @@ -1131,7 +1153,7 @@ Wallet.prototype.getKeyring = function getKeyring(address, callback) { if (!account) return callback(); - ring = account.derivePath(path, self.master.key); + ring = account.derivePath(path, self.master); callback(null, ring); }); @@ -1387,7 +1409,10 @@ Wallet.prototype.getRedeem = function getRedeem(hash, callback) { if (!account) return callback(); - ring = account.derivePath(path); + ring = account.derivePath(path, self.master); + + if (!ring) + return callback(); if (ring.program && hash.length === 20) { if (utils.equal(hash, ring.programHash)) @@ -1453,7 +1478,7 @@ Wallet.prototype.sign = function sign(tx, options, callback) { if (err) return callback(err); - self.deriveInputs(tx, master, function(err, rings) { + self.deriveInputs(tx, function(err, rings) { if (err) return callback(err); @@ -2540,15 +2565,32 @@ Account.prototype.deriveChange = function deriveChange(index, master) { return this.deriveAddress(true, index, master); }; +/** + * Derive an address from `path` object. + * @param {Path} path + * @param {MasterKey} master + * @returns {KeyRing} + */ + Account.prototype.derivePath = function derivePath(path, master) { - var ring, script; + var ring, script, raw; // Imported key. if (path.index === -1) { assert(path.imported); assert(this.n === 1); - ring = bcoin.keyring.fromRaw(path.imported); + + raw = path.imported; + + if (path.encrypted) + raw = master.decipher(raw, path.hash); + + if (!raw) + return; + + ring = bcoin.keyring.fromRaw(raw); ring.path = path; + return ring; } @@ -2574,33 +2616,43 @@ Account.prototype.deriveAddress = function deriveAddress(change, index, master, change = +change; - if (master) { - key = master.deriveAccount44(this.accountIndex); + if (master && master.key) { + key = master.key.deriveAccount44(this.accountIndex); key = key.derive(change).derive(index); } else { key = this.accountKey.derive(change).derive(index); } + ring = bcoin.keyring.fromPublic(key.publicKey, this.network); + ring.witness = this.witness; + if (script) { // Custom redeem script. - assert(this.n === 1); - ring = bcoin.keyring.fromScript(key.publicKey, script, this.network); + assert(this.type === Account.types.PUBKEYHASH); + ring.script = script; } else { - keys.push(key.publicKey); + switch (this.type) { + case Account.types.PUBKEYHASH: + break; + case Account.types.MULTISIG: + keys.push(key.publicKey); - for (i = 0; i < this.keys.length; i++) { - shared = this.keys[i]; - shared = shared.derive(change).derive(index); - keys.push(shared.publicKey); + for (i = 0; i < this.keys.length; i++) { + shared = this.keys[i]; + shared = shared.derive(change).derive(index); + keys.push(shared.publicKey); + } + + ring.script = bcoin.script.fromMultisig(this.m, this.n, keys); + + break; } - - ring = bcoin.keyring.fromAccount(this, key, keys, change, index); } - if (master) + if (key.privateKey) ring.privateKey = key.privateKey; - ring.path = bcoin.walletdb.Path.fromAccount(this, ring, change, index); + ring.path = bcoin.path.fromAccount(this, ring, change, index); return ring; }; @@ -2908,6 +2960,7 @@ function MasterKey(options) { this.ciphertext = null; this.key = null; + this.aesKey = null; this.timer = null; this.until = 0; this._destroy = this.destroy.bind(this); @@ -2989,7 +3042,7 @@ MasterKey.prototype.unlock = function unlock(passphrase, timeout, callback) { assert(this.encrypted); - utils.decrypt(this.ciphertext, passphrase, this.iv, function(err, data) { + utils.decrypt(this.ciphertext, passphrase, this.iv, function(err, data, key) { if (err) return callback(err); @@ -3001,6 +3054,8 @@ MasterKey.prototype.unlock = function unlock(passphrase, timeout, callback) { self.start(timeout); + self.aesKey = key; + callback(null, self.key); }); }; @@ -3037,6 +3092,26 @@ MasterKey.prototype.stop = function stop() { } }; +MasterKey.prototype.encipher = function encipher(data, iv) { + if (!this.aesKey) + return; + + if (typeof iv === 'string') + iv = new Buffer(iv, 'hex'); + + return utils.encipher(data, this.aesKey, iv.slice(0, 16)); +}; + +MasterKey.prototype.decipher = function decipher(data, iv) { + if (!this.aesKey) + return; + + if (typeof iv === 'string') + iv = new Buffer(iv, 'hex'); + + return utils.decipher(data, this.aesKey, iv.slice(0, 16)); +}; + /** * Destroy the key by zeroing the * privateKey and chainCode. Stop @@ -3056,6 +3131,11 @@ MasterKey.prototype.destroy = function destroy() { this.key.destroy(true); this.key = null; } + + if (this.aesKey) { + this.aesKey.fill(0); + this.aesKey = null; + } }; /** diff --git a/lib/bcoin/walletdb.js b/lib/bcoin/walletdb.js index dbc2e6c7..4cb9028e 100644 --- a/lib/bcoin/walletdb.js +++ b/lib/bcoin/walletdb.js @@ -1591,9 +1591,10 @@ function Path() { this.wid = null; this.name = null; this.account = 0; - this.change = 0; - this.index = 0; + this.change = -1; + this.index = -1; + this.encrypted = false; this.imported = null; this.script = null; @@ -1627,6 +1628,7 @@ Path.prototype.fromRaw = function fromRaw(data) { this.script = p.readVarBytes(); break; case 1: + this.encrypted = p.readU8() === 1; this.imported = p.readVarBytes(); this.change = -1; this.index = -1; @@ -1676,6 +1678,7 @@ Path.prototype.toRaw = function toRaw(writer) { } else { assert(this.imported); p.writeU8(1); + p.writeU8(this.encrypted ? 1 : 0); p.writeVarBytes(this.imported); } @@ -1689,34 +1692,22 @@ Path.prototype.toRaw = function toRaw(writer) { }; /** - * Inject properties from keyring. + * Inject properties from account. * @private * @param {WalletID} wid * @param {KeyRing} ring */ -Path.prototype.fromKeyRing = function fromKeyRing(ring) { - this.wid = ring.wid; - this.name = ring.name; - this.account = ring.account; - this.change = ring.change; - this.index = ring.index; - - this.version = ring.witness ? 0 : -1; - this.type = ring.getType(); - - this.id = ring.id; - this.hash = ring.getHash('hex'); - - return this; -}; - Path.prototype.fromAccount = function fromAccount(account, ring, change, index) { this.wid = account.wid; this.name = account.name; this.account = account.accountIndex; - this.change = change; - this.index = index; + + if (change != null) + this.change = change; + + if (index != null) + this.index = index; this.version = ring.witness ? 0 : -1; this.type = ring.getType(); diff --git a/test/wallet-test.js b/test/wallet-test.js index 321aaf2a..8e064e50 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -1065,13 +1065,14 @@ describe('Wallet', function() { it('should import key', function(cb) { var key = bcoin.keyring.generate(); - walletdb.create(function(err, w1) { + walletdb.create({ passphrase: 'test' }, function(err, w1) { assert.ifError(err); - w1.importKey(key, function(err) { + w1.importKey('default', key, 'test', function(err) { assert.ifError(err); w1.getKeyring(key.getHash('hex'), function(err, k) { if (err) return callback(err); + assert.equal(k.getHash('hex'), key.getHash('hex')); // Coinbase