Skip to content

Commit

Permalink
Merge pull request #145 from handshake-org/create-rpcs
Browse files Browse the repository at this point in the history
wallet: support alternative signers

This PR introduces new RPC calls which create unsigned txs for each covenant type. It also adds a new WalletCoinView class that extends CoinView and stores HD paths. These paths allow alternative, HD wallets to create valid input signatures. The MTX class has also been updated to persist coin information during de/serialization. Finally, this commit adds support for unsigned auction transactions to the HTTP API.
  • Loading branch information
boymanjor authored Jun 11, 2019
2 parents 189f54d + a451b90 commit e1c0281
Show file tree
Hide file tree
Showing 24 changed files with 1,571 additions and 100 deletions.
22 changes: 22 additions & 0 deletions lib/coins/coinview.js
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,17 @@ class CoinView extends View {
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 @@ -378,6 +389,17 @@ class CoinView extends View {
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 @@ -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;
Expand All @@ -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
};
}

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 Address = require('./address');
const consensus = require('../protocol/consensus');
const policy = require('../protocol/policy');
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion lib/primitives/tx.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions lib/wallet/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -1633,6 +1633,7 @@ class TransactionOptions {
this.subtractFee = valid.bool('subtractFee');
this.subtractIndex = valid.i32('subtractIndex');
this.depth = valid.u32(['confirmations', 'depth']);
this.paths = valid.bool('paths');
this.outputs = [];

if (valid.has('outputs')) {
Expand Down
62 changes: 58 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;

/**
Expand Down Expand Up @@ -212,14 +213,23 @@ class Path extends bio.Struct {

/**
* 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 @@ -233,18 +243,62 @@ class Path extends bio.Struct {

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

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

/**
* 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

0 comments on commit e1c0281

Please sign in to comment.