Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add wallet HD paths to JSON received from create TX #1131

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion bin/bwallet-cli
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,9 @@ class CLI {
smart: this.config.bool('smart'),
rate: this.config.ufixed('rate', 8),
subtractFee: this.config.bool('subtract-fee'),
sign: this.config.bool('sign')
sign: this.config.bool('sign'),
template: this.config.bool('template'),
paths: this.config.bool('paths')
};

const tx = await this.wallet.createTX(options);
Expand Down
22 changes: 22 additions & 0 deletions lib/coins/coinview.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,17 @@ class CoinView {
return coins.getOutput(index);
}

/**
* Get an HD path by prevout.
* Implemented in {@link WalletCoinView}.
* @param {Outpoint} prevout
* @returns {null}
*/

getPath(prevout) {
return null;
}

/**
* Get coins height by prevout.
* @param {Outpoint} prevout
Expand Down Expand Up @@ -374,6 +385,17 @@ class CoinView {
return this.getOutput(input.prevout);
}

/**
* Get a single path by input.
* Implemented in {@link WalletCoinView}.
* @param {Input} input
* @returns {null}
*/

getPathFor(input) {
return null;
}

/**
* Get coins height by input.
* @param {Input} input
Expand Down
6 changes: 4 additions & 2 deletions lib/primitives/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -309,10 +309,11 @@ class Input {
* of little-endian uint256s.
* @param {Network} network
* @param {Coin} coin
* @param {Path} path
* @returns {Object}
*/

getJSON(network, coin) {
getJSON(network, coin, path) {
network = Network.get(network);

let addr;
Expand All @@ -328,7 +329,8 @@ class Input {
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
};
}

Expand Down
13 changes: 13 additions & 0 deletions lib/primitives/mtx.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const Output = require('./output');
const Coin = require('./coin');
const Outpoint = require('./outpoint');
const CoinView = require('../coins/coinview');
const Path = require('../wallet/path');
const WalletCoinView = require('../wallet/walletcoinview');
const consensus = require('../protocol/consensus');
const policy = require('../protocol/policy');
const Stack = require('../script/stack');
Expand Down Expand Up @@ -1405,6 +1407,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;
Expand Down
3 changes: 2 additions & 1 deletion lib/primitives/tx.js
Original file line number Diff line number Diff line change
Expand Up @@ -2188,7 +2188,8 @@ class TX {
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);
Expand Down
26 changes: 25 additions & 1 deletion lib/wallet/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -446,11 +446,13 @@ class HTTP extends Server {
selection: valid.str('selection'),
smart: valid.bool('smart'),
account: valid.str('account'),
locktime: valid.u64('locktime'),
sort: valid.bool('sort'),
subtractFee: valid.bool('subtractFee'),
subtractIndex: valid.i32('subtractIndex'),
depth: valid.u32(['confirmations', 'depth']),
useSelectEstimate: valid.bool('useSelectEstimate'),
paths: valid.bool('paths'),
outputs: []
};

Expand Down Expand Up @@ -494,12 +496,14 @@ class HTTP extends Server {
selection: valid.str('selection'),
smart: valid.bool('smart'),
account: valid.str('account'),
locktime: valid.u64('locktime'),
sort: valid.bool('sort'),
subtractFee: valid.bool('subtractFee'),
subtractIndex: valid.i32('subtractIndex'),
depth: valid.u32(['confirmations', 'depth']),
template: valid.bool('template', sign),
useSelectEstimate: valid.bool('useSelectEstimate'),
paths: valid.bool('paths'),
outputs: []
};

Expand Down Expand Up @@ -527,7 +531,11 @@ class HTTP extends Server {
if (sign)
await req.wallet.sign(tx, passphrase);

res.json(200, tx.getJSON(this.network));
const json = tx.getJSON(this.network);
if (options.paths)
await this.addOutputPaths(json, tx, req.wallet);

res.json(200, json);
});

// Sign TX
Expand Down Expand Up @@ -883,6 +891,22 @@ class HTTP extends Server {
});
}

/**
* Add wallet path information to JSON outputs
* @private
*/

async addOutputPaths(json, tx, wallet) {
for (let i = 0; i < tx.outputs.length; i++) {
const address = tx.outputs[i].getAddress();
const path = await wallet.getPath(address);
if (!path)
continue;
json.outputs[i].path = path.getJSON(this.network);
}
return json;
}

/**
* Initialize websockets.
* @private
Expand Down
72 changes: 68 additions & 4 deletions lib/wallet/path.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
const assert = require('bsert');
const bio = require('bufio');
const Address = require('../primitives/address');
const Network = require('../protocol/network');
const {encoding} = bio;
const {inspectSymbol} = require('../utils');

Expand Down Expand Up @@ -258,14 +259,23 @@ class Path {

/**
* Convert path object to string derivation path.
* @param {String|Network?} network - Network type.
* @returns {String}
*/

toPath() {
toPath(network) {
if (this.keyType !== Path.types.HD)
return null;

return `m/${this.account}'/${this.branch}/${this.index}`;
let prefix = 'm';

if (network) {
const purpose = 44;
network = Network.get(network);
prefix += `/${purpose}'/${network.keyPrefix.coinType}'`;
}

return `${prefix}/${this.account}'/${this.branch}/${this.index}`;
}

/**
Expand All @@ -279,18 +289,72 @@ class Path {

/**
* Convert path to a json-friendly object.
* @param {String|Network?} network - Network type.
* @returns {Object}
*/

toJSON() {
getJSON(network) {
return {
name: this.name,
account: this.account,
change: this.branch === 1,
derivation: this.toPath()
derivation: this.toPath(network)
};
}

/**
* Convert the path to an object suitable
* for JSON serialization.
* @returns {Object}
*/

toJSON() {
return this.getJSON();
}

/**
* Inject properties from a json object.
* @param {Object} json
* @returns {Path}
*/

static fromJSON(json) {
return new this().fromJSON(json);
}

/**
* Inject properties from a json object.
* @param {Object} json
* @returns {Path}
*/

fromJSON(json) {
assert(json && typeof json === 'object');
assert(json.derivation && typeof json.derivation === 'string');

// Note: this object is mutated below.
const path = json.derivation.split('/');

// Note: "m/X'/X'/X'/X/X" or "m/X'/X/X".
assert (path.length === 4 || path.length === 6);

const index = parseInt(path.pop(), 10);
const branch = parseInt(path.pop(), 10);
const account = parseInt(path.pop(), 10);

assert(account === json.account);
assert(branch === 0 || branch === 1);
assert(Boolean(branch) === json.change);
assert((index >>> 0) === index);

this.name = json.name;
this.account = account;
this.branch = branch;
this.index = index;

return this;
}

/**
* Inspect the path.
* @returns {String}
Expand Down
93 changes: 93 additions & 0 deletions lib/wallet/paths.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*!
* paths.js - paths object for hsd
* Copyright (c) 2019, Boyma Fahnbulleh (MIT License).
* https://github.com/handshake-org/hsd
*/

'use strict';

const assert = require('bsert');

/**
* Paths
* Represents the HD paths for coins in a single transaction.
* @alias module:wallet.Paths
* @property {Map[]} outputs - Paths.
*/

class Paths {
/**
* Create paths
* @constructor
*/

constructor() {
this.paths = new Map();
}

/**
* Add a single entry to the collection.
* @param {Number} index
* @param {Path} path
* @returns {Path}
*/

add(index, path) {
assert((index >>> 0) === index);
assert(path);
this.paths.set(index, path);
return path;
}

/**
* Test whether the collection has a path.
* @param {Number} index
* @returns {Boolean}
*/

has(index) {
return this.paths.has(index);
}

/**
* Get a path.
* @param {Number} index
* @returns {Path|null}
*/

get(index) {
return this.paths.get(index) || null;
}

/**
* Remove a path and return it.
* @param {Number} index
* @returns {Path|null}
*/

remove(index) {
const path = this.get(index);

if (!path)
return null;

this.paths.delete(index);

return path;
}

/**
* Test whether there are paths.
* @returns {Boolean}
*/

isEmpty() {
return this.paths.size === 0;
}
}

/*
* Expose
*/

module.exports = Paths;
Loading