From b85c2cc4f6f6ff36326bbca87cc7fe4cc65aea0d Mon Sep 17 00:00:00 2001 From: Boyma Fahnbulleh Date: Sat, 20 Apr 2019 17:21:41 -0700 Subject: [PATCH] mtx: support HD paths --- lib/primitives/input.js | 6 +++-- lib/primitives/mtx.js | 17 ++++++++++++-- lib/primitives/tx.js | 3 ++- lib/wallet/rpc.js | 13 +++++++++++ lib/wallet/wallet.js | 51 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 85 insertions(+), 5 deletions(-) diff --git a/lib/primitives/input.js b/lib/primitives/input.js index 22aa90b31..e920cfe1d 100644 --- a/lib/primitives/input.js +++ b/lib/primitives/input.js @@ -169,10 +169,11 @@ class Input extends bio.Struct { * for JSON serialization. * @param {Network} network * @param {Coin} coin + * @param {Path} path * @returns {Object} */ - getJSON(network, coin) { + getJSON(network, coin, path) { network = Network.get(network); let addr; @@ -187,7 +188,8 @@ class Input extends bio.Struct { witness: this.witness.toJSON(), sequence: this.sequence, address: addr, - coin: coin ? coin.getJSON(network, true) : undefined + coin: coin ? coin.getJSON(network, true) : undefined, + path: path ? path.getJSON(network) : undefined }; } diff --git a/lib/primitives/mtx.js b/lib/primitives/mtx.js index 930219d93..a88b6b5d9 100644 --- a/lib/primitives/mtx.js +++ b/lib/primitives/mtx.js @@ -15,7 +15,9 @@ const Input = require('./input'); const Output = require('./output'); const Coin = require('./coin'); const Outpoint = require('./outpoint'); -const CoinView = require('../coins/coinview'); +const CoinView = require('../coins/coinview.js'); +const Path = require('../wallet/path'); +const WalletCoinView = require('../wallet/walletcoinview.js'); const Address = require('./address'); const consensus = require('../protocol/consensus'); const policy = require('../protocol/policy'); @@ -31,7 +33,7 @@ const {types} = rules; * @alias module:primitives.MTX * @extends TX * @property {Number} changeIndex - * @property {CoinView} view + * @property {WalletCoinView} view */ class MTX extends TX { @@ -1343,6 +1345,17 @@ class MTX extends TX { coin.index = prevout.index; this.view.addCoin(coin); + + if (!input.path) + continue; + + if(!(this.view instanceof WalletCoinView)) + this.view = WalletCoinView.fromCoinView(this.view); + + const outpoint = Outpoint.fromJSON(prevout); + const path = Path.fromJSON(input.path); + + this.view.addPath(outpoint, path); } return this; diff --git a/lib/primitives/tx.js b/lib/primitives/tx.js index c391b9c7f..ebcd7d0b2 100644 --- a/lib/primitives/tx.js +++ b/lib/primitives/tx.js @@ -1748,7 +1748,8 @@ class TX extends bio.Struct { version: this.version, inputs: this.inputs.map((input) => { const coin = view ? view.getCoinFor(input) : null; - return input.getJSON(network, coin); + const path = view ? view.getPathFor(input) : null; + return input.getJSON(network, coin, path); }), outputs: this.outputs.map((output) => { return output.getJSON(network); diff --git a/lib/wallet/rpc.js b/lib/wallet/rpc.js index b600e4316..0443630df 100644 --- a/lib/wallet/rpc.js +++ b/lib/wallet/rpc.js @@ -1434,6 +1434,7 @@ class RPC extends RPCBase { const wallet = this.wallet; const options = { + paths: true, account: opts.account, subtractFee: opts.subtract, outputs: [{ @@ -1931,6 +1932,7 @@ class RPC extends RPCBase { const opts = this._validateOpen(args, help, 'createopen'); const wallet = this.wallet; const mtx = await wallet.createOpen(opts.name, opts.force, { + paths: true, account: opts.account }); @@ -1968,6 +1970,7 @@ class RPC extends RPCBase { const opts = this._validateBid(args, help, 'createbid'); const wallet = this.wallet; const mtx = await wallet.createBid(opts.name, opts.bid, opts.value, { + paths: true, account: opts.account }); @@ -2026,6 +2029,7 @@ class RPC extends RPCBase { if (!opts.name) { const mtx = await wallet.createRevealAll({ + paths: true, account: opts.account }); @@ -2036,6 +2040,7 @@ class RPC extends RPCBase { throw new RPCError(errs.TYPE_ERROR, 'Invalid name.'); const mtx = await wallet.createReveal(opts.name, { + paths: true, account: opts.account }); @@ -2078,6 +2083,7 @@ class RPC extends RPCBase { if (!opts.name) { const mtx = await wallet.createRedeemAll({ + paths: true, account: opts.account }); return mtx.getJSON(this.network); @@ -2087,6 +2093,7 @@ class RPC extends RPCBase { throw new RPCError(errs.TYPE_ERROR, 'Invalid name.'); const mtx = await wallet.createRedeem(opts.name, { + paths: true, account: opts.account }); @@ -2118,6 +2125,7 @@ class RPC extends RPCBase { const opts = this._validateUpdate(args, help, 'createupdate'); const wallet = this.wallet; const mtx = await wallet.createUpdate(opts.name, opts.resource, { + paths: true, account: opts.account }); @@ -2161,6 +2169,7 @@ class RPC extends RPCBase { const wallet = this.wallet; const opts = this._validateRenewal(args, help, 'createrenewal'); const mtx = await wallet.createRenewal(opts.name, { + paths: true, account: opts.account }); @@ -2195,6 +2204,7 @@ class RPC extends RPCBase { const opts = this._validateTransfer(args, help, 'createtransfer'); const wallet = this.wallet; const tx = await wallet.createTransfer(opts.name, opts.address, { + paths: true, account: opts.account }); @@ -2240,6 +2250,7 @@ class RPC extends RPCBase { const opts = this._validateCancel(args, help, 'createcancel'); const wallet = this.wallet; const mtx = await wallet.createCancel(opts.name, { + paths: true, account: opts.account }); @@ -2274,6 +2285,7 @@ class RPC extends RPCBase { const opts = this._validateFinalize(args, help, 'createfinalize'); const wallet = this.wallet; const mtx = await wallet.createFinalize(opts.name, { + paths: true, account: opts.account }); @@ -2308,6 +2320,7 @@ class RPC extends RPCBase { const opts = this._validateRevoke(args, help, 'createrevoke'); const wallet = this.wallet; const mtx = await wallet.createRevoke(opts.name, { + paths: true, account: opts.account }); diff --git a/lib/wallet/wallet.js b/lib/wallet/wallet.js index 18eeb92ca..323524d65 100644 --- a/lib/wallet/wallet.js +++ b/lib/wallet/wallet.js @@ -19,6 +19,7 @@ const common = require('./common'); const Address = require('../primitives/address'); const MTX = require('../primitives/mtx'); const Script = require('../script/script'); +const WalletCoinView = require('./walletcoinview'); const WalletKey = require('./walletkey'); const HD = require('../hd/hd'); const Output = require('../primitives/output'); @@ -2968,6 +2969,10 @@ class Wallet extends EventEmitter { assert(mtx.verifyInputs(this.wdb.height + 1, this.network), 'TX failed context check.'); + // Set the HD paths. + if (options.paths !== false) + mtx.view = await this.getWalletCoinView(mtx); + const total = await this.template(mtx); if (total === 0) @@ -3430,6 +3435,52 @@ class Wallet extends EventEmitter { return this.txdb.getCoinView(tx); } + /** + * Get a wallet coin viewpoint with HD paths. + * @param {TX} tx + * @returns {Promise} - Returns {@link WalletCoinView}. + */ + + async getWalletCoinView(tx) { + let view = tx.view; + + if (!tx.hasCoins(view)) + view = await this.txdb.getCoinView(tx); + + view = WalletCoinView.fromCoinView(view); + + for (const input of tx.inputs) { + const prevout = input.prevout; + const coin = view.getCoin(prevout); + const path = await this.getPath(coin.address); + + if (!path) + continue; + + const account = await this.getAccount(path.account); + + if (!account) + continue; + + // The account index in the db may be wrong. + // We must read it from the stored xpub to be + // sure of its correctness. + // + // For more details see: + // https://github.com/bcoin-org/bcoin/issues/698. + path.account = account.accountKey.childIndex; + + // Unharden the account index, if necessary. + if (path.account & HD.common.HARDENED) + path.account ^= HD.common.HARDENED; + + // Add path to the viewpoint. + view.addPath(prevout, path); + } + + return view; + } + /** * Get a historical coin viewpoint. * @param {TX} tx