Skip to content

Commit d052b0c

Browse files
Merge pull request #5533 from BitGo/BTC-1835-update-lightning-wallet
feat(sdk-core): move lightning specific wallet functions
2 parents a3f98fe + e63129d commit d052b0c

File tree

11 files changed

+303
-240
lines changed

11 files changed

+303
-240
lines changed

modules/bitgo/test/v2/unit/lightning/lightningWallets.ts

Lines changed: 93 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as assert from 'assert';
22
import { TestBitGo } from '@bitgo/sdk-test';
33
import * as nock from 'nock';
4-
import { BaseCoin, getLightningAuthKeychains, getLightningKeychain } from '@bitgo/sdk-core';
4+
import { BaseCoin } from '@bitgo/sdk-core';
55

66
import { BitGo, common, GenerateLightningWalletOptions, Wallet, Wallets } from '../../../../src';
77

@@ -219,19 +219,19 @@ describe('Lightning wallets', function () {
219219
};
220220

221221
it('should get lightning key', async function () {
222-
const wallet = new Wallet(bitgo, basecoin, walletData);
222+
const wallet = new Wallet(bitgo, basecoin, walletData).lightningV2();
223223

224224
const keyNock = nock(bgUrl)
225225
.get('/api/v2/' + coinName + '/key/abc')
226226
.reply(200, userKeyData);
227227

228-
const key = await getLightningKeychain(wallet);
228+
const key = await wallet.getLightningKeychain();
229229
assert.deepStrictEqual(key, userKeyData);
230230
keyNock.done();
231231
});
232232

233233
it('should get lightning auth keys', async function () {
234-
const wallet = new Wallet(bitgo, basecoin, walletData);
234+
const wallet = new Wallet(bitgo, basecoin, walletData).lightningV2();
235235

236236
const userAuthKeyNock = nock(bgUrl)
237237
.get('/api/v2/' + coinName + '/key/def')
@@ -240,57 +240,41 @@ describe('Lightning wallets', function () {
240240
.get('/api/v2/' + coinName + '/key/ghi')
241241
.reply(200, nodeAuthKeyData);
242242

243-
const { userAuthKey, nodeAuthKey } = await getLightningAuthKeychains(wallet);
243+
const { userAuthKey, nodeAuthKey } = await wallet.getLightningAuthKeychains();
244244
assert.deepStrictEqual(userAuthKey, userAuthKeyData);
245245
assert.deepStrictEqual(nodeAuthKey, nodeAuthKeyData);
246246
userAuthKeyNock.done();
247247
nodeAuthKeyNock.done();
248248
});
249249

250-
it('should fail to get lightning key for invalid coin', async function () {
251-
const wallet = new Wallet(bitgo, bitgo.coin('tltc'), walletData);
252-
await assert.rejects(
253-
async () => await getLightningKeychain(wallet),
254-
/Error: Invalid coin to get lightning Keychain: ltc/
255-
);
256-
});
257-
258-
it('should fail to get lightning auth keys for invalid coin', async function () {
259-
const wallet = new Wallet(bitgo, bitgo.coin('tltc'), walletData);
260-
await assert.rejects(
261-
async () => await getLightningAuthKeychains(wallet),
262-
/Error: Invalid coin to get lightning auth keychains: ltc/
263-
);
264-
});
265-
266250
it('should fail to get lightning key for invalid number of keys', async function () {
267-
const wallet = new Wallet(bitgo, basecoin, { ...walletData, keys: [] });
251+
const wallet = new Wallet(bitgo, basecoin, { ...walletData, keys: [] }).lightningV2();
268252
await assert.rejects(
269-
async () => await getLightningKeychain(wallet),
253+
async () => await wallet.getLightningKeychain(),
270254
/Error: Invalid number of key in lightning wallet: 0/
271255
);
272256
});
273257

274258
it('should fail to get lightning auth keys for invalid number of keys', async function () {
275-
const wallet = new Wallet(bitgo, basecoin, { ...walletData, coinSpecific: { keys: ['def'] } });
259+
const wallet = new Wallet(bitgo, basecoin, { ...walletData, coinSpecific: { keys: ['def'] } }).lightningV2();
276260
await assert.rejects(
277-
async () => await getLightningAuthKeychains(wallet),
261+
async () => await wallet.getLightningAuthKeychains(),
278262
/Error: Invalid number of auth keys in lightning wallet: 1/
279263
);
280264
});
281265

282266
it('should fail to get lightning key for invalid response', async function () {
283-
const wallet = new Wallet(bitgo, basecoin, walletData);
267+
const wallet = new Wallet(bitgo, basecoin, walletData).lightningV2();
284268

285269
nock(bgUrl)
286270
.get('/api/v2/' + coinName + '/key/abc')
287271
.reply(200, { ...userKeyData, source: 'backup' });
288272

289-
await assert.rejects(async () => await getLightningKeychain(wallet), /Error: Invalid user key/);
273+
await assert.rejects(async () => await wallet.getLightningKeychain(), /Error: Invalid user key/);
290274
});
291275

292276
it('should fail to get lightning auth keys for invalid response', async function () {
293-
const wallet = new Wallet(bitgo, basecoin, walletData);
277+
const wallet = new Wallet(bitgo, basecoin, walletData).lightningV2();
294278

295279
nock(bgUrl)
296280
.get('/api/v2/' + coinName + '/key/def')
@@ -301,9 +285,89 @@ describe('Lightning wallets', function () {
301285
.reply(200, nodeAuthKeyData);
302286

303287
await assert.rejects(
304-
async () => await getLightningAuthKeychains(wallet),
288+
async () => await wallet.getLightningAuthKeychains(),
305289
/Error: Invalid lightning auth key: def/
306290
);
307291
});
308292
});
293+
294+
describe('Update lightning wallet coin specific', function () {
295+
const walletData = {
296+
id: 'fakeid',
297+
coin: coinName,
298+
keys: ['abc'],
299+
coinSpecific: { keys: ['def', 'ghi'] },
300+
};
301+
302+
const userAuthKey = {
303+
id: 'def',
304+
pub: 'xpub661MyMwAqRbcGYjYsnsDj1SHdiXynWEXNnfNgMSpokN54FKyMqbu7rWEfVNDs6uAJmz86UVFtq4sefhQpXZhSAzQcL9zrEPtiLNNZoeSxCG',
305+
encryptedPrv:
306+
'{"iv":"zYhhaNdW0wPfJEoBjZ4pvg==","v":1,"iter":10000,"ks":256,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"tgAMua9jjhw=","ct":"HcrbxQvNlWG5tLMndYzdNCYa1l+1h7o+vSsweA0+q1le3tWt6jLUJSEjZN+JI8lTZ2KPFQgLulQQhsUa+ytUCBi0vSgjF7x7CprT7l2Cfjkew00XsEd7wnmtJUsrQk8m69Co7tIRA3oEgzrnYwy4qOM81lbNNyQ="}',
307+
source: 'user',
308+
coinSpecific: {
309+
tlnbtc: {
310+
purpose: 'userAuth',
311+
},
312+
},
313+
};
314+
315+
const nodeAuthKey = {
316+
id: 'ghi',
317+
pub: 'xpub661MyMwAqRbcG9xnTnAnRbJPo3MAHyRtH4zeehN8exYk4VFz5buepUzebhix33BKhS5Eb4V3LEfW5pYiSR8qmaEnyrpeghhKY8JfzAsUDpq',
318+
encryptedPrv:
319+
'{"iv":"bH6eGbnl9x8PZECPrgvcng==","v":1,"iter":10000,"ks":256,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"o8yknV6nTI8=","ct":"nGyzAToIzYkQeIdcVafoWHtMx7+Fgj0YldCme3WA1yxJAA0QulZVhblMZN/7efCRIumA0NNmpH7dxH6n8cVlz/Z+RUgC2q9lgvZKUoJcYNTjWUfkmkJutXX2tr8yVxm+eC/hnRiyfVLZ2qPxctvDlBVBfgLuPyc="}',
320+
source: 'user',
321+
coinSpecific: {
322+
tlnbtc: {
323+
purpose: 'nodeAuth',
324+
},
325+
},
326+
};
327+
328+
const watchOnlyAccounts = {
329+
master_key_birthday_timestamp: 'dummy',
330+
master_key_fingerprint: 'dummy',
331+
accounts: [
332+
{
333+
xpub: 'upub5Eep7H5q39PzQZLVEYLBytDyBNeV74E8mQsyeL6UozFq9Y3MsZ52G7YGuqrJPgoyAqF7TBeJdnkrHrVrB5pkWkPJ9cJGAePMU6F1Gyw6aFH',
334+
purpose: 49,
335+
coin_type: 0,
336+
account: 0,
337+
},
338+
{
339+
xpub: 'vpub5ZU1PHGpQoDSHckYico4nsvwsD3mTh6UjqL5zyGWXZXzBjTYMNKot7t9eRPQY71hJcnNN9r1ss25g3xA9rmoJ5nWPg8jEWavrttnsVa1qw1',
340+
purpose: 84,
341+
coin_type: 0,
342+
account: 0,
343+
},
344+
],
345+
};
346+
347+
const params = {
348+
encryptedSignerMacaroon: 'test encryptedSignerMacaroon',
349+
encryptedSignerAdminMacaroon: 'test encryptedSignerAdminMacaroon',
350+
signerIp: 'test signerIp',
351+
encryptedSignerTlsKey: 'test encryptedSignerTlsKey',
352+
signerTlsCert: 'test signerTlsCert',
353+
watchOnlyAccounts,
354+
};
355+
356+
it('should update wallet', async function () {
357+
const wallet = new Wallet(bitgo, basecoin, walletData).lightningV2();
358+
359+
const userAuthKeyNock = nock(bgUrl)
360+
.get('/api/v2/' + coinName + '/key/def')
361+
.reply(200, userAuthKey);
362+
const nodeAuthKeyNock = nock(bgUrl)
363+
.get('/api/v2/' + coinName + '/key/ghi')
364+
.reply(200, nodeAuthKey);
365+
const wpWalletUpdateNock = nock(bgUrl).put(`/api/v2/tlnbtc/wallet/${walletData.id}`).reply(200);
366+
367+
await assert.doesNotReject(async () => await wallet.updateWalletCoinSpecific(params, 'password123'));
368+
userAuthKeyNock.done();
369+
nodeAuthKeyNock.done();
370+
wpWalletUpdateNock.done();
371+
});
372+
});
309373
});

modules/express/src/lightning/codecs.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,19 @@ export const GetWalletStateResponse = t.type(
3333

3434
export type GetWalletStateResponse = t.TypeOf<typeof GetWalletStateResponse>;
3535

36-
export const InitLightningWalletRequest = t.strict(
37-
{
38-
walletId: t.string,
39-
passphrase: t.string,
40-
signerIP: IPAddress,
41-
signerTlsCert: t.string,
42-
signerTlsKey: t.string,
43-
expressIP: IPAddress,
44-
},
36+
export const InitLightningWalletRequest = t.intersection(
37+
[
38+
t.strict({
39+
walletId: t.string,
40+
passphrase: t.string,
41+
signerIp: IPAddress,
42+
signerTlsCert: t.string,
43+
expressIp: IPAddress,
44+
}),
45+
t.partial({
46+
signerTlsKey: t.string,
47+
}),
48+
],
4549
'InitLightningWalletRequest'
4650
);
4751

@@ -51,7 +55,7 @@ export const CreateSignerMacaroonRequest = t.strict(
5155
{
5256
walletId: t.string,
5357
passphrase: t.string,
54-
watchOnlyIP: IPAddress,
58+
watchOnlyIp: IPAddress,
5559
},
5660
'CreateSignerMacaroonRequest'
5761
);

modules/express/src/lightning/lightningSignerRoutes.ts

Lines changed: 23 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
import * as express from 'express';
22
import {
33
decodeOrElse,
4-
createMessageSignature,
54
getUtxolibNetwork,
65
signerMacaroonPermissions,
76
createWatchOnly,
87
addIPCaveatToMacaroon,
9-
getLightningAuthKeychains,
10-
getLightningKeychain,
11-
updateLightningWallet,
12-
LightningWalletCoinSpecific,
138
isLightningCoinName,
149
deriveLightningServiceSharedSecret,
1510
} from '@bitgo/sdk-core';
@@ -27,12 +22,12 @@ import { LndSignerClient } from './lndSignerClient';
2722
type Decrypt = (params: { input: string; password: string }) => string;
2823

2924
async function createSignerMacaroon(
30-
watchOnlyIP: string,
25+
watchOnlyIp: string,
3126
header: { adminMacaroonHex: string },
3227
lndSignerClient: LndSignerClient
3328
) {
3429
const { macaroon } = await lndSignerClient.bakeMacaroon({ permissions: signerMacaroonPermissions }, header);
35-
const macaroonBase64 = addIPCaveatToMacaroon(Buffer.from(macaroon, 'hex').toString('base64'), watchOnlyIP);
30+
const macaroonBase64 = addIPCaveatToMacaroon(Buffer.from(macaroon, 'hex').toString('base64'), watchOnlyIp);
3631
return Buffer.from(macaroonBase64, 'base64').toString('hex');
3732
}
3833

@@ -58,7 +53,7 @@ function getMacaroonRootKey(passphrase: string, nodeAuthEncryptedPrv: string, de
5853
* Handle the request to initialise remote signer LND for a wallet.
5954
*/
6055
export async function handleInitLightningWallet(req: express.Request): Promise<unknown> {
61-
const { walletId, passphrase, signerTlsKey, signerTlsCert, signerIP, expressIP } = decodeOrElse(
56+
const { walletId, passphrase, signerTlsKey, signerTlsCert, signerIp, expressIp } = decodeOrElse(
6257
InitLightningWalletRequest.name,
6358
InitLightningWalletRequest,
6459
req.body,
@@ -77,10 +72,10 @@ export async function handleInitLightningWallet(req: express.Request): Promise<u
7772
}
7873
const coin = bitgo.coin(coinName);
7974

80-
const wallet = await coin.wallets().get({ id: walletId });
75+
const lightningWallet = (await coin.wallets().get({ id: walletId })).lightningV2();
8176

82-
const userKey = await getLightningKeychain(wallet);
83-
const { userAuthKey, nodeAuthKey } = await getLightningAuthKeychains(wallet);
77+
const userKey = await lightningWallet.getLightningKeychain();
78+
const { nodeAuthKey } = await lightningWallet.getLightningAuthKeychains();
8479

8580
const network = getUtxolibNetwork(coin.getChain());
8681
const signerRootKey = getSignerRootKey(passphrase, userKey.encryptedPrv, network, bitgo.decrypt);
@@ -94,38 +89,28 @@ export async function handleInitLightningWallet(req: express.Request): Promise<u
9489

9590
const encryptedSignerAdminMacaroon = bitgo.encrypt({
9691
password: passphrase,
97-
input: addIPCaveatToMacaroon(adminMacaroon, expressIP),
92+
input: addIPCaveatToMacaroon(adminMacaroon, expressIp),
9893
});
99-
const encryptedSignerTlsKey = bitgo.encrypt({ password: passphrase, input: signerTlsKey });
100-
const watchOnly = createWatchOnly(signerRootKey, network);
94+
const watchOnlyAccounts = createWatchOnly(signerRootKey, network);
95+
const encryptedSignerTlsKey = signerTlsKey ? bitgo.encrypt({ password: passphrase, input: signerTlsKey }) : undefined;
10196

102-
const coinSpecific = {
103-
[coin.getChain()]: {
97+
return await lightningWallet.updateWalletCoinSpecific(
98+
{
10499
encryptedSignerAdminMacaroon,
105-
signerIP,
100+
signerIp,
106101
signerTlsCert,
107-
encryptedSignerTlsKey,
108-
watchOnly,
102+
watchOnlyAccounts,
103+
...(encryptedSignerTlsKey && { encryptedSignerTlsKey }),
109104
},
110-
};
111-
112-
if (!LightningWalletCoinSpecific.is(coinSpecific)) {
113-
throw new Error('Invalid lightning wallet coin specific data');
114-
}
115-
116-
const signature = createMessageSignature(
117-
coinSpecific,
118-
bitgo.decrypt({ password: passphrase, input: userAuthKey.encryptedPrv })
105+
passphrase
119106
);
120-
121-
return await updateLightningWallet(wallet, { coinSpecific, signature });
122107
}
123108

124109
/**
125110
* Handle the request to create a signer macaroon from remote signer LND for a wallet.
126111
*/
127112
export async function handleCreateSignerMacaroon(req: express.Request): Promise<unknown> {
128-
const { walletId, passphrase, watchOnlyIP } = decodeOrElse(
113+
const { walletId, passphrase, watchOnlyIp } = decodeOrElse(
129114
CreateSignerMacaroonRequest.name,
130115
CreateSignerMacaroonRequest,
131116
req.body,
@@ -145,17 +130,18 @@ export async function handleCreateSignerMacaroon(req: express.Request): Promise<
145130
const coin = bitgo.coin(coinName);
146131

147132
const wallet = await coin.wallets().get({ id: walletId });
133+
const lightningWallet = wallet.lightningV2();
148134

149135
const encryptedSignerAdminMacaroon = wallet.coinSpecific()?.encryptedSignerAdminMacaroon;
150136
if (!encryptedSignerAdminMacaroon) {
151137
throw new Error('Missing encryptedSignerAdminMacaroon in wallet');
152138
}
153139
const adminMacaroon = bitgo.decrypt({ password: passphrase, input: encryptedSignerAdminMacaroon });
154140

155-
const { userAuthKey } = await getLightningAuthKeychains(wallet);
141+
const { userAuthKey } = await lightningWallet.getLightningAuthKeychains();
156142

157143
const signerMacaroon = await createSignerMacaroon(
158-
watchOnlyIP,
144+
watchOnlyIp,
159145
{ adminMacaroonHex: Buffer.from(adminMacaroon, 'base64').toString('hex') },
160146
lndSignerClient
161147
);
@@ -166,20 +152,12 @@ export async function handleCreateSignerMacaroon(req: express.Request): Promise<
166152
password: deriveLightningServiceSharedSecret(coinName, userAuthXprv).toString('hex'),
167153
input: signerMacaroon,
168154
});
169-
170-
const coinSpecific = {
171-
[coin.getChain()]: {
155+
return await lightningWallet.updateWalletCoinSpecific(
156+
{
172157
encryptedSignerMacaroon,
173158
},
174-
};
175-
176-
if (!LightningWalletCoinSpecific.is(coinSpecific)) {
177-
throw new Error('Invalid lightning wallet coin specific data');
178-
}
179-
180-
const signature = createMessageSignature(coinSpecific, userAuthXprv);
181-
182-
return await updateLightningWallet(wallet, { coinSpecific, signature });
159+
passphrase
160+
);
183161
}
184162

185163
/**

modules/express/test/unit/clientRoutes/lightning/lightningSignerFixture.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ export const apiData = {
22
initWalletRequestBody: {
33
walletId: 'fakeid',
44
passphrase: 'password123',
5-
expressIP: '127.0.0.1',
6-
signerIP: '127.0.0.1',
5+
expressIp: '127.0.0.1',
6+
signerIp: '127.0.0.1',
77
signerTlsCert:
88
'LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNQRENDQWVLZ0F3SUJBZ0lSQU02TEFoaGxOMGo4ZlhxV2dLTWdENmN3Q2dZSUtvWkl6ajBFQXdJd09ERWYKTUIwR0ExVUVDaE1XYkc1a0lHRjFkRzluWlc1bGNtRjBaV1FnWTJWeWRERVZNQk1HQTFVRUF4TU1aV1UxTVdZeApOREV4TUdVMk1CNFhEVEkwTURneE9ERXlNVE14TWxvWERUSTFNVEF4TXpFeU1UTXhNbG93T0RFZk1CMEdBMVVFCkNoTVdiRzVrSUdGMWRHOW5aVzVsY21GMFpXUWdZMlZ5ZERFVk1CTUdBMVVFQXhNTVpXVTFNV1l4TkRFeE1HVTIKTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFclA0d2NXWFEwUWFFazhsVFNVTXBCa1d3ditFbQpxNTNyOWVSeVJUOTRkZGdVR0tTMFlRK0liZzFseVBRU3hiN0dXYloyWG9GUFdiK1VOM0lFMVlMQ2thT0J6RENCCnlUQU9CZ05WSFE4QkFmOEVCQU1DQXFRd0V3WURWUjBsQkF3d0NnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC8KQkFVd0F3RUIvekFkQmdOVkhRNEVGZ1FVb3JmUkNVQytmaUNjZlE4cEhEUTFWaE1uMXBBd2NnWURWUjBSQkdzdwphWUlNWldVMU1XWXhOREV4TUdVMmdnbHNiMk5oYkdodmMzU0NDbk5wWjI1bGNtNXZaR1dDQ1d4dlkyRnNhRzl6CmRJSUVkVzVwZUlJS2RXNXBlSEJoWTJ0bGRJSUhZblZtWTI5dWJvY0Vmd0FBQVljUUFBQUFBQUFBQUFBQUFBQUEKQUFBQUFZY0VyQlFBQWpBS0JnZ3Foa2pPUFFRREFnTklBREJGQWlFQXJuQ0xRTlgzeDZ1NjhIM2xCOG9wOUFKaApBd2RrUjhXOXNSaUZnZDJKM2tZQ0lHczFOVGM0T0toRzByNzVHUWpXb2x0SkJyOUtjWWVyR1V3aklCaCtvZ1h0Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K',
99
signerTlsKey:
@@ -12,7 +12,7 @@ export const apiData = {
1212
signerMacaroonRequestBody: {
1313
walletId: 'fakeid',
1414
passphrase: 'password123',
15-
watchOnlyIP: '127.0.0.1',
15+
watchOnlyIp: '127.0.0.1',
1616
},
1717
unlockWalletRequestBody: {
1818
walletId: 'fakeid',

0 commit comments

Comments
 (0)