Skip to content

Commit

Permalink
feat(express): lightning wallet creation step two
Browse files Browse the repository at this point in the history
initialize signer lnd and update platform.

Ticket: BTC-1356
  • Loading branch information
saravanan7mani committed Aug 12, 2024
1 parent 19c7408 commit c43000b
Show file tree
Hide file tree
Showing 13 changed files with 616 additions and 45 deletions.
6 changes: 5 additions & 1 deletion modules/express/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
},
"dependencies": {
"@bitgo/sdk-core": "^27.8.0",
"@bitgo/utxo-lib": "^10.1.0",
"argparse": "^1.0.10",
"bitgo": "^38.18.0",
"bluebird": "^3.5.3",
Expand All @@ -48,7 +49,10 @@
"express": "^4.17.3",
"lodash": "^4.17.20",
"morgan": "^1.9.1",
"superagent": "^9.0.1"
"superagent": "^9.0.1",
"io-ts": "2.1.3",
"io-ts-types": "0.5.16",
"macaroon": "^3.0.4"
},
"devDependencies": {
"@bitgo/public-types": "2.33.4",
Expand Down
4 changes: 4 additions & 0 deletions modules/express/src/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,8 @@ parser.addArgument(['--signerMode'], {
parser.addArgument(['--signerFileSystemPath'], {
help: 'Local path specifying where an Express signer machine keeps encrypted user private keys.',
});

parser.addArgument(['--lightningSignerFileSystemPath'], {
help: 'Local path specifying where an Express machine keeps lightning signer urls.',
});
export const args = () => parser.parseArgs();
10 changes: 10 additions & 0 deletions modules/express/src/clientRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { Config } from './config';
import { ApiResponseError } from './errors';
import { promises as fs } from 'fs';
import { retryPromise } from './retryPromise';
import { handleInitLightningWallet } from './lightning/lightningRoutes';

const { version } = require('bitgo/package.json');
const pjson = require('../package.json');
Expand Down Expand Up @@ -1658,3 +1659,12 @@ export function setupSigningRoutes(app: express.Application, config: Config): vo
promiseWrapper(handleV2OFCSignPayloadInExtSigningMode)
);
}

export function setupLightningRoutes(app: express.Application, config: Config): void {
app.post(
'/api/v2/:coin/initlightningwallet/:id',
parseBody,
prepareBitGo(config),
promiseWrapper(handleInitLightningWallet)
);
}
22 changes: 18 additions & 4 deletions modules/express/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { isNil, isNumber } from 'lodash';
import 'dotenv/config';

import { args } from './args';
import { getLightningSignerConnections } from './lightning/lightningUtils';
import { LightningSignerConnections } from './lightning/codecs';

function readEnvVar(name, ...deprecatedAliases): string | undefined {
if (process.env[name] !== undefined && process.env[name] !== '') {
Expand Down Expand Up @@ -38,6 +40,8 @@ export interface Config {
externalSignerUrl?: string;
signerMode?: boolean;
signerFileSystemPath?: string;
lightningSignerFileSystemPath?: string;
lightningSignerConnections?: LightningSignerConnections;
}

export const ArgConfig = (args): Partial<Config> => ({
Expand All @@ -59,6 +63,7 @@ export const ArgConfig = (args): Partial<Config> => ({
externalSignerUrl: args.externalSignerUrl,
signerMode: args.signerMode,
signerFileSystemPath: args.signerFileSystemPath,
lightningSignerFileSystemPath: args.lightningSignerFileSystemPath,
});

export const EnvConfig = (): Partial<Config> => ({
Expand All @@ -80,6 +85,7 @@ export const EnvConfig = (): Partial<Config> => ({
externalSignerUrl: readEnvVar('BITGO_EXTERNAL_SIGNER_URL'),
signerMode: readEnvVar('BITGO_SIGNER_MODE') ? true : undefined,
signerFileSystemPath: readEnvVar('BITGO_SIGNER_FILE_SYSTEM_PATH'),
lightningSignerFileSystemPath: readEnvVar('BITGO_LIGHTNING_SIGNER_FILE_SYSTEM_PATH'),
});

export const DefaultConfig: Config = {
Expand All @@ -102,7 +108,7 @@ export const DefaultConfig: Config = {
* @param url
* @return {string}
*/
function _forceSecureUrl(url: string): string {
export function _forceSecureUrl(url: string): string {
const regex = new RegExp(/(^\w+:|^)\/\//);
if (regex.test(url)) {
return url.replace(/(^\w+:|^)\/\//, 'https://');
Expand All @@ -115,7 +121,7 @@ function _forceSecureUrl(url: string): string {
*
* Later configs have higher precedence over earlier configs.
*/
function mergeConfigs(...configs: Partial<Config>[]): Config {
async function mergeConfigs(...configs: Partial<Config>[]): Promise<Config> {
function isNilOrNaN(val: unknown): val is null | undefined | number {
return isNil(val) || (isNumber(val) && isNaN(val));
}
Expand All @@ -142,6 +148,12 @@ function mergeConfigs(...configs: Partial<Config>[]): Config {
}
}

const lightningSignerFileSystemPath = get('lightningSignerFileSystemPath');
let lightningSignerConnections: LightningSignerConnections | undefined;
if (lightningSignerFileSystemPath) {
lightningSignerConnections = await getLightningSignerConnections(lightningSignerFileSystemPath);
}

return {
port: get('port'),
bind: get('bind'),
Expand All @@ -161,11 +173,13 @@ function mergeConfigs(...configs: Partial<Config>[]): Config {
externalSignerUrl,
signerMode: get('signerMode'),
signerFileSystemPath: get('signerFileSystemPath'),
lightningSignerFileSystemPath,
lightningSignerConnections,
};
}

export const config = () => {
export const config = async () => {
const arg = ArgConfig(args());
const env = EnvConfig();
return mergeConfigs(env, arg);
return await mergeConfigs(env, arg);
};
10 changes: 8 additions & 2 deletions modules/express/src/expressApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ function createHttpServer(app: express.Application): http.Server {
*/
export function startup(config: Config, baseUri: string): () => void {
return function () {
const { env, ipc, customRootUri, customBitcoinNetwork, signerMode } = config;
const { env, ipc, customRootUri, customBitcoinNetwork, signerMode, lightningSignerFileSystemPath } = config;
/* eslint-disable no-console */
console.log('BitGo-Express running');
console.log(`Environment: ${env}`);
Expand All @@ -122,6 +122,9 @@ export function startup(config: Config, baseUri: string): () => void {
if (signerMode) {
console.log(`External signer mode: ${signerMode}`);
}
if (lightningSignerFileSystemPath) {
console.log(`Lightning signer file system path: ${lightningSignerFileSystemPath}`);
}
/* eslint-enable no-console */
};
}
Expand Down Expand Up @@ -240,6 +243,9 @@ export function setupRoutes(app: express.Application, config: Config): void {
} else {
clientRoutes.setupAPIRoutes(app, config);
}
if (config.lightningSignerFileSystemPath) {
clientRoutes.setupLightningRoutes(app, config);
}
}

export function app(cfg: Config): express.Application {
Expand Down Expand Up @@ -308,7 +314,7 @@ export async function prepareIpc(ipcSocketFilePath: string) {
}

export async function init(): Promise<void> {
const cfg = config();
const cfg = await config();
const expressApp = app(cfg);

const server = await createServer(cfg, expressApp);
Expand Down
99 changes: 99 additions & 0 deletions modules/express/src/lightning/codecs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import * as t from 'io-ts';
import { isIP } from 'net';
import { NonEmptyString } from 'io-ts-types';

function getCodecPair<C extends t.Mixed>(innerCodec: C): t.UnionC<[t.TypeC<{ lnbtc: C }>, t.TypeC<{ tlnbtc: C }>]> {
return t.union([t.type({ lnbtc: innerCodec }), t.type({ tlnbtc: innerCodec })]);
}

export const IPCustomCodec = new t.Type<string, string, unknown>(
'IPAddress',
t.string.is,
(input, context) => (typeof input === 'string' && isIP(input) ? t.success(input) : t.failure(input, context)),
t.identity
);

export type IPAddress = t.TypeOf<typeof IPCustomCodec>;

export const LightningSignerDetailsCodec = t.type({
url: t.string,
tlsCert: t.string,
});

export type LightningSignerDetails = t.TypeOf<typeof LightningSignerDetailsCodec>;

export const LightningSignerConnectionsCodec = t.record(t.string, LightningSignerDetailsCodec);

export type LightningSignerConnections = t.TypeOf<typeof LightningSignerConnectionsCodec>;

export const KeyPurposeCodec = t.union([t.literal('userAuth'), t.literal('nodeAuth')], 'KeyPurpose');

export type KeyPurpose = t.TypeOf<typeof KeyPurposeCodec>;

export const LightningAuthKeychainCoinSpecificCodec = getCodecPair(t.type({ purpose: KeyPurposeCodec }));

export const LightningKeychainCodec = t.strict(
{
id: NonEmptyString,
pub: NonEmptyString,
encryptedPrv: NonEmptyString,
coinSpecific: t.undefined,
source: t.literal('user'),
},
'LightningKeychain'
);

export type LightningKeychain = t.TypeOf<typeof LightningKeychainCodec>;

export const LightningAuthKeychainCodec = t.strict(
{
id: NonEmptyString,
pub: NonEmptyString,
encryptedPrv: NonEmptyString,
coinSpecific: LightningAuthKeychainCoinSpecificCodec,
source: t.literal('user'),
},
'LightningAuthKeychain'
);

export type LightningAuthKeychain = t.TypeOf<typeof LightningAuthKeychainCodec>;

export const GetWalletStateResponseCodec = t.type(
{
state: t.number,
},
'GetWalletStateResponse'
);

export type GetWalletStateResponse = t.TypeOf<typeof GetWalletStateResponseCodec>;

export const InitLightningWalletRequestCodec = t.strict(
{
passphrase: NonEmptyString,
signerIP: IPCustomCodec,
signerTlsCert: NonEmptyString,
signerTlsKey: NonEmptyString,
watchOnlyIP: IPCustomCodec,
},
'InitLightningWalletRequest'
);

export type InitLightningWalletRequest = t.TypeOf<typeof InitLightningWalletRequestCodec>;

export const InitWalletResponseCodec = t.type(
{
admin_macaroon: t.string,
},
'InitWalletResponse'
);

export type InitWalletResponse = t.TypeOf<typeof InitWalletResponseCodec>;

export const BakeMacaroonResponseCodec = t.type(
{
macaroon: t.string,
},
'BakeMacaroonResponse'
);

export type BakeMacaroonResponse = t.TypeOf<typeof BakeMacaroonResponseCodec>;
Loading

0 comments on commit c43000b

Please sign in to comment.