Skip to content

Commit 99f4f3d

Browse files
authored
Merge pull request #5550 from BitGo/revert-5545-BTC-0.revert-breaking-ln-changes
Move self custodial lightning logic to abstract-lightning
2 parents 867da94 + e924320 commit 99f4f3d

File tree

34 files changed

+4173
-3902
lines changed

34 files changed

+4173
-3902
lines changed

modules/abstract-lightning/package.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
"types": "./dist/src/index.d.ts",
77
"scripts": {
88
"build": "yarn tsc --build --incremental --verbose .",
9+
"test": "yarn unit-test",
10+
"unit-test": "nyc -- mocha --recursive test",
911
"fmt": "prettier --write .",
1012
"check-fmt": "prettier --check .",
1113
"clean": "rm -r ./dist",
@@ -38,7 +40,13 @@
3840
},
3941
"dependencies": {
4042
"@bitgo/sdk-core": "^28.25.0",
41-
"@bitgo/utxo-lib": "^11.2.2"
43+
"@bitgo/utxo-lib": "^11.2.2",
44+
"@bitgo/statics": "^51.0.0",
45+
"fp-ts": "^2.12.2",
46+
"io-ts": "npm:@bitgo-forks/[email protected]",
47+
"io-ts-types": "^0.5.16",
48+
"bs58check": "^2.1.2",
49+
"macaroon": "^3.0.4"
4250
},
4351
"gitHead": "18e460ddf02de2dbf13c2aa243478188fb539f0c"
4452
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
export * from './abstractLightningCoin';
2+
export * from './wallet';
3+
export * from './lightning';

modules/sdk-core/src/bitgo/lightning/codecs.ts renamed to modules/abstract-lightning/src/lightning/codecs.ts

Lines changed: 26 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -71,33 +71,21 @@ export const WatchOnly = t.type({
7171

7272
export type WatchOnly = t.TypeOf<typeof WatchOnly>;
7373

74-
export const LightningWalletCoinSpecific = getCodecPair(
75-
t.partial({
76-
encryptedSignerAdminMacaroon: t.string,
77-
signerIP: IPAddress,
78-
signerTlsCert: t.string,
79-
encryptedSignerTlsKey: t.string,
80-
watchOnly: WatchOnly,
81-
encryptedSignerMacaroon: t.string,
82-
})
83-
);
84-
85-
export type LightningWalletCoinSpecific = t.TypeOf<typeof LightningWalletCoinSpecific>;
86-
87-
export const UpdateLightningWallet = t.partial(
88-
{
89-
coinSpecific: LightningWalletCoinSpecific,
90-
signature: t.string,
91-
},
92-
'UpdateLightningWallet'
93-
);
74+
export const UpdateLightningWalletSignedRequest = t.partial({
75+
encryptedSignerMacaroon: t.string,
76+
encryptedSignerAdminMacaroon: t.string,
77+
signerIp: t.string,
78+
encryptedSignerTlsKey: t.string,
79+
signerTlsCert: t.string,
80+
watchOnlyAccounts: WatchOnly,
81+
});
9482

95-
export type UpdateLightningWallet = t.TypeOf<typeof UpdateLightningWallet>;
83+
export type UpdateLightningWalletSignedRequest = t.TypeOf<typeof UpdateLightningWalletSignedRequest>;
9684

9785
export const LndAmount = t.strict(
9886
{
99-
sat: t.string,
100-
msat: t.string,
87+
sat: BigIntFromString,
88+
msat: BigIntFromString,
10189
},
10290
'LndAmount'
10391
);
@@ -127,11 +115,11 @@ export type ChannelBalance = t.TypeOf<typeof ChannelBalance>;
127115
export const LndWalletBalance = t.strict(
128116
{
129117
/** Total balance, confirmed and unconfirmed */
130-
totalBalance: t.string,
131-
confirmedBalance: t.string,
132-
unconfirmedBalance: t.string,
133-
lockedBalance: t.string,
134-
reservedBalanceAnchorChan: t.string,
118+
totalBalance: BigIntFromString,
119+
confirmedBalance: BigIntFromString,
120+
unconfirmedBalance: BigIntFromString,
121+
lockedBalance: BigIntFromString,
122+
reservedBalanceAnchorChan: BigIntFromString,
135123
},
136124
'LndWalletBalance'
137125
);
@@ -151,7 +139,7 @@ export const LndBalance = t.strict(
151139
{
152140
offchain: ChannelBalance,
153141
onchain: LndWalletBalance,
154-
totalLimboBalance: t.string,
142+
totalLimboBalance: BigIntFromString,
155143
},
156144
'LndBalance'
157145
);
@@ -160,16 +148,16 @@ export type LndBalance = t.TypeOf<typeof LndBalance>;
160148

161149
export const LndGetBalancesResponse = t.strict(
162150
{
163-
inboundBalance: t.string,
164-
inboundPendingBalance: t.string,
165-
inboundUnsettledBalance: t.string,
166-
outboundBalance: t.string,
167-
outboundPendingBalance: t.string,
168-
outboundUnsettledBalance: t.string,
151+
inboundBalance: BigIntFromString,
152+
inboundPendingBalance: BigIntFromString,
153+
inboundUnsettledBalance: BigIntFromString,
154+
outboundBalance: BigIntFromString,
155+
outboundPendingBalance: BigIntFromString,
156+
outboundUnsettledBalance: BigIntFromString,
169157
// wallet balances, names forced by type in AbstractCoin
170-
spendableBalanceString: t.string,
171-
balanceString: t.string,
172-
confirmedBalanceString: t.string,
158+
spendableBalanceString: BigIntFromString,
159+
balanceString: BigIntFromString,
160+
confirmedBalanceString: BigIntFromString,
173161
},
174162
'LndGetBalancesResponse'
175163
);
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
export * from './signableJson';
22
export * from './signature';
33
export * from './lightningUtils';
4-
export * from './lightningWallet';
54
export * from './codecs';

modules/sdk-core/src/bitgo/lightning/lightningUtils.ts renamed to modules/abstract-lightning/src/lightning/lightningUtils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as utxolib from '@bitgo/utxo-lib';
33
import { importMacaroon, bytesToBase64 } from 'macaroon';
44
import * as bs58check from 'bs58check';
55
import { WatchOnly, WatchOnlyAccount } from './codecs';
6-
import { getSharedSecret } from '../ecdh';
6+
import * as sdkcore from '@bitgo/sdk-core';
77

88
// https://github.com/lightningnetwork/lnd/blob/master/docs/remote-signing.md#the-signer-node
99
export const signerMacaroonPermissions = [
@@ -204,5 +204,5 @@ export function createWatchOnly(signerRootKey: string, network: utxolib.Network)
204204
export function deriveLightningServiceSharedSecret(coinName: 'lnbtc' | 'tlnbtc', userAuthXprv: string): Buffer {
205205
const publicKey = Buffer.from(getStaticsLightningNetwork(coinName).lightningServicePubKey, 'hex');
206206
const userAuthHdNode = utxolib.bip32.fromBase58(userAuthXprv);
207-
return getSharedSecret(userAuthHdNode, publicKey);
207+
return sdkcore.getSharedSecret(userAuthHdNode, publicKey);
208208
}

modules/sdk-core/src/bitgo/lightning/signableJson.ts renamed to modules/abstract-lightning/src/lightning/signableJson.ts

File renamed without changes.

modules/sdk-core/src/bitgo/lightning/signature.ts renamed to modules/abstract-lightning/src/lightning/signature.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as utxolib from '@bitgo/utxo-lib';
2+
import * as sdkcore from '@bitgo/sdk-core';
23
import { canonicalizeObject, Signable } from './signableJson';
3-
import { signMessage, verifyMessage } from '../bip32util';
44

55
/**
66
* Verifies a signature for a given message.
@@ -20,7 +20,7 @@ export function verifyMessageSignature(
2020
const messageString = JSON.stringify(canonicalizeObject(message));
2121
const pubKey = utxolib.bip32.fromBase58(pub, network).publicKey;
2222
const signatureBuffer = Buffer.from(signature, 'hex');
23-
return verifyMessage(messageString, pubKey, signatureBuffer, network);
23+
return sdkcore.verifyMessage(messageString, pubKey, signatureBuffer, network);
2424
}
2525

2626
/**
@@ -33,10 +33,10 @@ export function verifyMessageSignature(
3333
*/
3434
export function createMessageSignature(
3535
message: Signable,
36-
prv: string,
36+
xprv: string,
3737
network: utxolib.Network = utxolib.networks.bitcoin
3838
): string {
3939
const requestString = JSON.stringify(canonicalizeObject(message));
40-
const prvKey = utxolib.bip32.fromBase58(prv, network);
41-
return signMessage(requestString, prvKey, network).toString('hex');
40+
const prvKey = utxolib.bip32.fromBase58(xprv, network);
41+
return sdkcore.signMessage(requestString, prvKey, network).toString('hex');
4242
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './lightning';
2+
export * from './wallet';
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import * as sdkcore from '@bitgo/sdk-core';
2+
import {
3+
createMessageSignature,
4+
LightningAuthKeychain,
5+
LightningKeychain,
6+
unwrapLightningCoinSpecific,
7+
UpdateLightningWalletSignedRequest,
8+
} from '../lightning';
9+
10+
export interface ILightningWallet {
11+
/**
12+
* Creates a lightning invoice
13+
* @param params Invoice parameters (to be defined)
14+
*/
15+
createInvoice(params: unknown): Promise<unknown>;
16+
17+
/**
18+
* Pay a lightning invoice
19+
* @param params Payment parameters (to be defined)
20+
*/
21+
payInvoice(params: unknown): Promise<unknown>;
22+
23+
/**
24+
* Get the lightning keychain for the given wallet.
25+
*/
26+
getLightningKeychain(): Promise<LightningKeychain>;
27+
28+
/**
29+
* Get the lightning auth keychains for the given wallet.
30+
*/
31+
getLightningAuthKeychains(): Promise<{ userAuthKey: LightningAuthKeychain; nodeAuthKey: LightningAuthKeychain }>;
32+
33+
/**
34+
* Updates the coin-specific configuration for a Lightning Wallet.
35+
*
36+
* @param {UpdateLightningWalletSignedRequest} params - The parameters containing the updated wallet-specific details.
37+
* - `encryptedSignerMacaroon` (optional): This macaroon is used by the watch-only node to ask the signer node to sign transactions.
38+
* Encrypted with ECDH secret key from private key of wallet's user auth key and public key of lightning service.
39+
* - `encryptedSignerAdminMacaroon` (optional): Generated when initializing the wallet of the signer node.
40+
* Encrypted with client's wallet passphrase.
41+
* - `signerIp` (optional): The IP address of the Lightning signer node.
42+
* - `encryptedSignerTlsKey` (optional): The wallet passphrase encrypted TLS key of the signer.
43+
* - `signerTlsCert` (optional): The TLS certificate of the signer.
44+
* - `watchOnlyAccounts` (optional): These are the accounts used to initialize the watch-only wallet.
45+
* @param {string} passphrase - wallet passphrase.
46+
* @returns {Promise<unknown>} A promise resolving to the updated wallet response or throwing an error if the update fails.
47+
*/
48+
updateWalletCoinSpecific(params: UpdateLightningWalletSignedRequest, passphrase: string): Promise<unknown>;
49+
}
50+
51+
export class SelfCustodialLightningWallet implements ILightningWallet {
52+
public wallet: sdkcore.IWallet;
53+
54+
constructor(wallet: sdkcore.IWallet) {
55+
const coin = wallet.baseCoin;
56+
if (coin.getFamily() !== 'lnbtc') {
57+
throw new Error(`Invalid coin to update lightning wallet: ${coin.getFamily()}`);
58+
}
59+
this.wallet = wallet;
60+
}
61+
62+
async createInvoice(params: unknown): Promise<unknown> {
63+
throw new Error('Method not implemented.');
64+
}
65+
66+
async payInvoice(params: unknown): Promise<unknown> {
67+
throw new Error('Method not implemented.');
68+
}
69+
70+
async getLightningKeychain(): Promise<LightningKeychain> {
71+
const keyIds = this.wallet.keyIds();
72+
if (keyIds.length !== 1) {
73+
throw new Error(`Invalid number of key in lightning wallet: ${keyIds.length}`);
74+
}
75+
const keychain = await this.wallet.baseCoin.keychains().get({ id: keyIds[0] });
76+
return sdkcore.decodeOrElse(LightningKeychain.name, LightningKeychain, keychain, (_) => {
77+
// DON'T throw errors from decodeOrElse. It could leak sensitive information.
78+
throw new Error(`Invalid user key`);
79+
});
80+
}
81+
82+
async getLightningAuthKeychains(): Promise<{
83+
userAuthKey: LightningAuthKeychain;
84+
nodeAuthKey: LightningAuthKeychain;
85+
}> {
86+
const authKeyIds = this.wallet.coinSpecific()?.keys;
87+
if (authKeyIds?.length !== 2) {
88+
throw new Error(`Invalid number of auth keys in lightning wallet: ${authKeyIds?.length}`);
89+
}
90+
const coin = this.wallet.baseCoin;
91+
const keychains = await Promise.all(authKeyIds.map((id) => coin.keychains().get({ id })));
92+
const authKeychains = keychains.map((keychain) => {
93+
return sdkcore.decodeOrElse(LightningAuthKeychain.name, LightningAuthKeychain, keychain, (_) => {
94+
// DON'T throw errors from decodeOrElse. It could leak sensitive information.
95+
throw new Error(`Invalid lightning auth key: ${keychain?.id}`);
96+
});
97+
});
98+
const [userAuthKey, nodeAuthKey] = (['userAuth', 'nodeAuth'] as const).map((purpose) => {
99+
const keychain = authKeychains.find(
100+
(k) => unwrapLightningCoinSpecific(k.coinSpecific, coin.getChain()).purpose === purpose
101+
);
102+
if (!keychain) {
103+
throw new Error(`Missing ${purpose} key`);
104+
}
105+
return keychain;
106+
});
107+
108+
return { userAuthKey, nodeAuthKey };
109+
}
110+
111+
async updateWalletCoinSpecific(params: UpdateLightningWalletSignedRequest, passphrase: string): Promise<unknown> {
112+
sdkcore.decodeOrElse(
113+
UpdateLightningWalletSignedRequest.name,
114+
UpdateLightningWalletSignedRequest,
115+
params,
116+
(errors) => {
117+
// DON'T throw errors from decodeOrElse. It could leak sensitive information.
118+
throw new Error(`Invalid params for lightning specific update wallet: ${errors}`);
119+
}
120+
);
121+
const { userAuthKey } = await this.getLightningAuthKeychains();
122+
const signature = createMessageSignature(
123+
params,
124+
this.wallet.bitgo.decrypt({ password: passphrase, input: userAuthKey.encryptedPrv })
125+
);
126+
const coinSpecific = {
127+
[this.wallet.coin()]: {
128+
signedRequest: params,
129+
signature,
130+
},
131+
};
132+
return await this.wallet.bitgo.put(this.wallet.url()).send({ coinSpecific }).result();
133+
}
134+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import * as sdkcore from '@bitgo/sdk-core';
2+
import { isLightningCoinName } from '../lightning';
3+
import { ILightningWallet, SelfCustodialLightningWallet } from './lightning';
4+
5+
/**
6+
* Return a lightwallet instance if the coin supports it
7+
*/
8+
9+
export function getLightningWallet(wallet: sdkcore.IWallet): ILightningWallet {
10+
if (!isLightningCoinName(wallet.baseCoin.getChain())) {
11+
throw new Error(`Lightning not supported for ${wallet.coin()}`);
12+
}
13+
return new SelfCustodialLightningWallet(wallet);
14+
}

0 commit comments

Comments
 (0)