Skip to content

Commit e1c0281

Browse files
authored
Merge pull request #145 from handshake-org/create-rpcs
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.
2 parents 189f54d + a451b90 commit e1c0281

24 files changed

+1571
-100
lines changed

lib/coins/coinview.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,17 @@ class CoinView extends View {
298298
return coins.getOutput(index);
299299
}
300300

301+
/**
302+
* Get an HD path by prevout.
303+
* Implemented in {@link WalletCoinView}.
304+
* @param {Outpoint} prevout
305+
* @returns {null}
306+
*/
307+
308+
getPath(prevout) {
309+
return null;
310+
}
311+
301312
/**
302313
* Get coins height by prevout.
303314
* @param {Outpoint} prevout
@@ -378,6 +389,17 @@ class CoinView extends View {
378389
return this.getOutput(input.prevout);
379390
}
380391

392+
/**
393+
* Get a single path by input.
394+
* Implemented in {@link WalletCoinView}.
395+
* @param {Input} input
396+
* @returns {null}
397+
*/
398+
399+
getPathFor(input) {
400+
return null;
401+
}
402+
381403
/**
382404
* Get coins height by input.
383405
* @param {Input} input

lib/primitives/input.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,10 +169,11 @@ class Input extends bio.Struct {
169169
* for JSON serialization.
170170
* @param {Network} network
171171
* @param {Coin} coin
172+
* @param {Path} path
172173
* @returns {Object}
173174
*/
174175

175-
getJSON(network, coin) {
176+
getJSON(network, coin, path) {
176177
network = Network.get(network);
177178

178179
let addr;
@@ -187,7 +188,8 @@ class Input extends bio.Struct {
187188
witness: this.witness.toJSON(),
188189
sequence: this.sequence,
189190
address: addr,
190-
coin: coin ? coin.getJSON(network, true) : undefined
191+
coin: coin ? coin.getJSON(network, true) : undefined,
192+
path: path ? path.getJSON(network) : undefined
191193
};
192194
}
193195

lib/primitives/mtx.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ const Output = require('./output');
1616
const Coin = require('./coin');
1717
const Outpoint = require('./outpoint');
1818
const CoinView = require('../coins/coinview');
19+
const Path = require('../wallet/path');
20+
const WalletCoinView = require('../wallet/walletcoinview');
1921
const Address = require('./address');
2022
const consensus = require('../protocol/consensus');
2123
const policy = require('../protocol/policy');
@@ -1343,6 +1345,17 @@ class MTX extends TX {
13431345
coin.index = prevout.index;
13441346

13451347
this.view.addCoin(coin);
1348+
1349+
if (!input.path)
1350+
continue;
1351+
1352+
if(!(this.view instanceof WalletCoinView))
1353+
this.view = WalletCoinView.fromCoinView(this.view);
1354+
1355+
const outpoint = Outpoint.fromJSON(prevout);
1356+
const path = Path.fromJSON(input.path);
1357+
1358+
this.view.addPath(outpoint, path);
13461359
}
13471360

13481361
return this;

lib/primitives/tx.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1748,7 +1748,8 @@ class TX extends bio.Struct {
17481748
version: this.version,
17491749
inputs: this.inputs.map((input) => {
17501750
const coin = view ? view.getCoinFor(input) : null;
1751-
return input.getJSON(network, coin);
1751+
const path = view ? view.getPathFor(input) : null;
1752+
return input.getJSON(network, coin, path);
17521753
}),
17531754
outputs: this.outputs.map((output) => {
17541755
return output.getJSON(network);

lib/wallet/http.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1633,6 +1633,7 @@ class TransactionOptions {
16331633
this.subtractFee = valid.bool('subtractFee');
16341634
this.subtractIndex = valid.i32('subtractIndex');
16351635
this.depth = valid.u32(['confirmations', 'depth']);
1636+
this.paths = valid.bool('paths');
16361637
this.outputs = [];
16371638

16381639
if (valid.has('outputs')) {

lib/wallet/path.js

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
const assert = require('bsert');
1010
const bio = require('bufio');
1111
const Address = require('../primitives/address');
12+
const Network = require('../protocol/network');
1213
const {encoding} = bio;
1314

1415
/**
@@ -212,14 +213,23 @@ class Path extends bio.Struct {
212213

213214
/**
214215
* Convert path object to string derivation path.
216+
* @param {String|Network?} network - Network type.
215217
* @returns {String}
216218
*/
217219

218-
toPath() {
220+
toPath(network) {
219221
if (this.keyType !== Path.types.HD)
220222
return null;
221223

222-
return `m/${this.account}'/${this.branch}/${this.index}`;
224+
let prefix = 'm';
225+
226+
if (network) {
227+
const purpose = 44;
228+
network = Network.get(network);
229+
prefix += `/${purpose}'/${network.keyPrefix.coinType}'`;
230+
}
231+
232+
return `${prefix}/${this.account}'/${this.branch}/${this.index}`;
223233
}
224234

225235
/**
@@ -233,18 +243,62 @@ class Path extends bio.Struct {
233243

234244
/**
235245
* Convert path to a json-friendly object.
246+
* @param {String|Network?} network - Network type.
236247
* @returns {Object}
237248
*/
238249

239-
getJSON() {
250+
getJSON(network) {
240251
return {
241252
name: this.name,
242253
account: this.account,
243254
change: this.branch === 1,
244-
derivation: this.toPath()
255+
derivation: this.toPath(network)
245256
};
246257
}
247258

259+
/**
260+
* Inject properties from a json object.
261+
* @param {Object} json
262+
* @returns {Path}
263+
*/
264+
265+
static fromJSON(json) {
266+
return new this().fromJSON(json);
267+
}
268+
269+
/**
270+
* Inject properties from a json object.
271+
* @param {Object} json
272+
* @returns {Path}
273+
*/
274+
275+
fromJSON(json) {
276+
assert(json && typeof json === 'object');
277+
assert(json.derivation && typeof json.derivation === 'string');
278+
279+
// Note: this object is mutated below.
280+
const path = json.derivation.split('/');
281+
282+
// Note: "m/X'/X'/X'/X/X" or "m/X'/X/X".
283+
assert (path.length === 4 || path.length === 6);
284+
285+
const index = parseInt(path.pop(), 10);
286+
const branch = parseInt(path.pop(), 10);
287+
const account = parseInt(path.pop(), 10);
288+
289+
assert(account === json.account);
290+
assert(branch === 0 || branch === 1);
291+
assert(Boolean(branch) === json.change);
292+
assert((index >>> 0) === index);
293+
294+
this.name = json.name;
295+
this.account = account;
296+
this.branch = branch;
297+
this.index = index;
298+
299+
return this;
300+
}
301+
248302
/**
249303
* Inspect the path.
250304
* @returns {String}

lib/wallet/paths.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*!
2+
* paths.js - paths object for hsd
3+
* Copyright (c) 2019, Boyma Fahnbulleh (MIT License).
4+
* https://github.com/handshake-org/hsd
5+
*/
6+
7+
'use strict';
8+
9+
const assert = require('bsert');
10+
11+
/**
12+
* Paths
13+
* Represents the HD paths for coins in a single transaction.
14+
* @alias module:wallet.Paths
15+
* @property {Map[]} outputs - Paths.
16+
*/
17+
18+
class Paths {
19+
/**
20+
* Create paths
21+
* @constructor
22+
*/
23+
24+
constructor() {
25+
this.paths = new Map();
26+
}
27+
28+
/**
29+
* Add a single entry to the collection.
30+
* @param {Number} index
31+
* @param {Path} path
32+
* @returns {Path}
33+
*/
34+
35+
add(index, path) {
36+
assert((index >>> 0) === index);
37+
assert(path);
38+
this.paths.set(index, path);
39+
return path;
40+
}
41+
42+
/**
43+
* Test whether the collection has a path.
44+
* @param {Number} index
45+
* @returns {Boolean}
46+
*/
47+
48+
has(index) {
49+
return this.paths.has(index);
50+
}
51+
52+
/**
53+
* Get a path.
54+
* @param {Number} index
55+
* @returns {Path|null}
56+
*/
57+
58+
get(index) {
59+
return this.paths.get(index) || null;
60+
}
61+
62+
/**
63+
* Remove a path and return it.
64+
* @param {Number} index
65+
* @returns {Path|null}
66+
*/
67+
68+
remove(index) {
69+
const path = this.get(index);
70+
71+
if (!path)
72+
return null;
73+
74+
this.paths.delete(index);
75+
76+
return path;
77+
}
78+
79+
/**
80+
* Test whether there are paths.
81+
* @returns {Boolean}
82+
*/
83+
84+
isEmpty() {
85+
return this.paths.size === 0;
86+
}
87+
}
88+
89+
/*
90+
* Expose
91+
*/
92+
93+
module.exports = Paths;

0 commit comments

Comments
 (0)