diff --git a/lib/bcoin/http/rpc.js b/lib/bcoin/http/rpc.js index 86b3cbc9..0169390f 100644 --- a/lib/bcoin/http/rpc.js +++ b/lib/bcoin/http/rpc.js @@ -3555,7 +3555,7 @@ RPC.prototype.listunspent = function listunspent(args, callback) { txid: utils.revHex(coin.hash), vout: coin.index, address: address ? address.toBase58(self.network) : null, - account: ring ? ring.name : undefined, + account: ring ? ring.path.name : undefined, redeemScript: ring && ring.script ? ring.script.toJSON() : undefined, diff --git a/lib/bcoin/keyring.js b/lib/bcoin/keyring.js index ec6fb17f..c5fd9e10 100644 --- a/lib/bcoin/keyring.js +++ b/lib/bcoin/keyring.js @@ -36,13 +36,7 @@ function KeyRing(options, network) { this.publicKey = null; this.privateKey = null; this.script = null; - - this.wid = 0; - this.id = null; - this.name = null; - this.account = 0; - this.change = 0; - this.index = 0; + this.path = null; this._keyHash = null; this._keyAddress = null; @@ -351,13 +345,6 @@ KeyRing.prototype.fromAccount = function fromAccount(account, key, keys, change, this.witness = account.witness; - this.wid = account.wid; - this.id = account.id; - this.name = account.name; - this.account = account.accountIndex; - this.change = change; - this.index = index; - return this; }; @@ -818,12 +805,12 @@ KeyRing.prototype.toJSON = function toJSON() { key: this.publicKey.toString('hex'), script: this.script ? this.script.toRaw().toString('hex') : null, type: constants.scriptTypesByVal[this.type].toLowerCase(), - wid: this.wid, - id: this.id, - name: this.name, - account: this.account, - change: this.change, - index: this.index, + wid: this.path ? this.path.wid : undefined, + id: this.path ? this.path.id : undefined, + name: this.path ? this.path.name : undefined, + account: this.path ? this.path.account : undefined, + change: this.path ? this.path.change : undefined, + index: this.path ? this.path.index : undefined, address: this.getAddress('base58'), programAddress: this.getProgramAddress('base58') }; @@ -884,14 +871,12 @@ KeyRing.prototype.toRaw = function toRaw(writer) { var p = new BufferWriter(writer); var i; - p.writeU32(this.network.magic); p.writeU8(this.witness ? 1 : 0); - p.writeVarBytes(this.publicKey); if (this.privateKey) p.writeVarBytes(this.privateKey); else - p.writeVarint(0); + p.writeVarBytes(this.publicKey); if (this.script) p.writeVarBytes(this.script.toRaw()); @@ -912,16 +897,18 @@ KeyRing.prototype.toRaw = function toRaw(writer) { KeyRing.prototype.fromRaw = function fromRaw(data) { var p = new BufferReader(data); - var i, count; + var i, count, key; - this.network = bcoin.network.fromMagic(p.readU32()); this.witness = p.readU8() === 1; - this.publicKey = p.readVarBytes(); - this.privateKey = p.readVarBytes(); + key = p.readVarBytes(); - if (this.privateKey.length === 0) - this.privateKey = null; + if (key.length === 32) { + this.privateKey = key; + this.publicKey = bcoin.ec.publicKeyCreate(key, true); + } else { + this.publicKey = key; + } this.script = p.readVarBytes(); diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 99da3435..3480be2d 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -628,6 +628,46 @@ 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 @@ -1049,7 +1089,7 @@ Wallet.prototype.deriveInputs = function deriveInputs(tx, master, callback) { if (!account) return next(); - ring = account.deriveAddress(path.change, path.index, master); + ring = account.derivePath(path, master); rings.push(ring); next(); @@ -1091,7 +1131,7 @@ Wallet.prototype.getKeyring = function getKeyring(address, callback) { if (!account) return callback(); - ring = account.deriveAddress(path.change, path.index, self.master.key); + ring = account.derivePath(path, self.master.key); callback(null, ring); }); @@ -1347,7 +1387,7 @@ Wallet.prototype.getRedeem = function getRedeem(hash, callback) { if (!account) return callback(); - ring = account.deriveAddress(path.change, path.index); + ring = account.derivePath(path); if (ring.program && hash.length === 20) { if (utils.equal(hash, ring.programHash)) @@ -2500,6 +2540,27 @@ Account.prototype.deriveChange = function deriveChange(index, master) { return this.deriveAddress(true, index, master); }; +Account.prototype.derivePath = function derivePath(path, master) { + var ring, script; + + // Imported key. + if (path.index === -1) { + assert(path.imported); + assert(this.n === 1); + ring = bcoin.keyring.fromRaw(path.imported); + ring.path = path; + return ring; + } + + // Custom redeem script. + if (path.script) + script = new bcoin.script(path.script); + + ring = this.deriveAddress(path.change, path.index, master, script); + + return ring; +}; + /** * Derive an address at `index`. Do not increment depth. * @param {Boolean} change - Whether the address on the change branch. @@ -2507,7 +2568,7 @@ Account.prototype.deriveChange = function deriveChange(index, master) { * @returns {KeyRing} */ -Account.prototype.deriveAddress = function deriveAddress(change, index, master) { +Account.prototype.deriveAddress = function deriveAddress(change, index, master, script) { var keys = []; var i, key, shared, ring; @@ -2520,19 +2581,27 @@ Account.prototype.deriveAddress = function deriveAddress(change, index, master) key = this.accountKey.derive(change).derive(index); } - keys.push(key.publicKey); + if (script) { + // Custom redeem script. + assert(this.n === 1); + ring = bcoin.keyring.fromScript(key.publicKey, script, this.network); + } else { + 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 = bcoin.keyring.fromAccount(this, key, keys, change, index); } - ring = bcoin.keyring.fromAccount(this, key, keys, change, index); - if (master) ring.privateKey = key.privateKey; + ring.path = bcoin.walletdb.Path.fromAccount(this, ring, change, index); + return ring; }; diff --git a/lib/bcoin/walletdb.js b/lib/bcoin/walletdb.js index 715ab31d..dbc2e6c7 100644 --- a/lib/bcoin/walletdb.js +++ b/lib/bcoin/walletdb.js @@ -946,7 +946,7 @@ WalletDB.prototype.saveAddress = function saveAddress(wid, rings, callback) { for (i = 0; i < rings.length; i++) { ring = rings[i]; - path = Path.fromKeyRing(ring); + path = ring.path; items.push([ring.getAddress(), path]); @@ -1594,6 +1594,9 @@ function Path() { this.change = 0; this.index = 0; + this.imported = null; + this.script = null; + // Currently unused. this.type = bcoin.script.types.PUBKEYHASH; this.version = -1; @@ -1615,8 +1618,24 @@ Path.prototype.fromRaw = function fromRaw(data) { this.wid = p.readU32(); this.name = p.readVarString('utf8'); this.account = p.readU32(); - this.change = p.readU32(); - this.index = p.readU32(); + + switch (p.readU8()) { + case 0: + this.change = p.readU32(); + this.index = p.readU32(); + if (p.left() > 0) + this.script = p.readVarBytes(); + break; + case 1: + this.imported = p.readVarBytes(); + this.change = -1; + this.index = -1; + break; + default: + assert(false); + break; + } + this.version = p.readU8(); this.type = p.readU8(); @@ -1647,8 +1666,19 @@ Path.prototype.toRaw = function toRaw(writer) { p.writeU32(this.wid); p.writeVarString(this.name, 'utf8'); p.writeU32(this.account); - p.writeU32(this.change); - p.writeU32(this.index); + + if (this.index !== -1) { + p.writeU8(0); + p.writeU32(this.change); + p.writeU32(this.index); + if (this.script) + p.writeVarBytes(this.script); + } else { + assert(this.imported); + p.writeU8(1); + p.writeVarBytes(this.imported); + } + p.writeU8(this.version === -1 ? 0xff : this.version); p.writeU8(this.type); @@ -1681,6 +1711,26 @@ Path.prototype.fromKeyRing = function fromKeyRing(ring) { 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; + + this.version = ring.witness ? 0 : -1; + this.type = ring.getType(); + + this.id = account.id; + this.hash = ring.getHash('hex'); + + return this; +}; + +Path.fromAccount = function fromAccount(account, ring, change, index) { + return new Path().fromAccount(account, ring, change, index); +}; + /** * Instantiate path from keyring. * @param {WalletID} wid diff --git a/test/wallet-test.js b/test/wallet-test.js index 52565167..321aaf2a 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -1063,6 +1063,58 @@ describe('Wallet', function() { }); }); + it('should import key', function(cb) { + var key = bcoin.keyring.generate(); + walletdb.create(function(err, w1) { + assert.ifError(err); + w1.importKey(key, 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 + var t1 = bcoin.mtx() + .addOutput(key.getAddress(), 5460) + .addOutput(key.getAddress(), 5460) + .addOutput(key.getAddress(), 5460) + .addOutput(key.getAddress(), 5460); + + t1.addInput(dummyInput); + t1 = t1.toTX(); + + walletdb.addTX(t1, function(err) { + assert.ifError(err); + + w1.getTX(t1.hash('hex'), function(err, tx) { + assert.ifError(err); + assert(tx); + assert.equal(t1.hash('hex'), tx.hash('hex')); + + var options = { + rate: 10000, + round: true, + outputs: [{ address: w1.getAddress(), value: 7000 }] + }; + + // Create new transaction + w1.createTX(options, function(err, t2) { + assert.ifError(err); + w1.sign(t2, function(err) { + assert.ifError(err); + assert(t2.verify()); + assert(t2.inputs[0].prevout.hash === tx.hash('hex')); + cb(); + }); + }); + }); + }); + }); + }); + }); + }); + it('should cleanup', function(cb) { walletdb.dump(function(err, records) { assert.ifError(err);