Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(express): lightning wallet creation step two #4783

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 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": "^28.2.0",
"@bitgo/utxo-lib": "^10.2.0",
"argparse": "^1.0.10",
"bitgo": "^39.1.1",
"bluebird": "^3.5.3",
Expand All @@ -48,7 +49,9 @@
"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"
},
"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();
11 changes: 11 additions & 0 deletions modules/express/src/clientRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ import { Config } from './config';
import { ApiResponseError } from './errors';
import { promises as fs } from 'fs';
import { retryPromise } from './retryPromise';
import {
handleCreateSignerMacaroon,
handleGetLightningWalletState,
handleInitLightningWallet,
} from './lightning/lightningRoutes';

const { version } = require('bitgo/package.json');
const pjson = require('../package.json');
Expand Down Expand Up @@ -1651,3 +1656,9 @@ 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/initwallet', parseBody, prepareBitGo(config), promiseWrapper(handleInitLightningWallet));
app.post('/api/v2/:coin/signermacaroon', parseBody, prepareBitGo(config), promiseWrapper(handleCreateSignerMacaroon));
app.get('/api/v2/:coin/wallet/:id/state', prepareBitGo(config), promiseWrapper(handleGetLightningWalletState));
}
6 changes: 5 additions & 1 deletion modules/express/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export interface Config {
externalSignerUrl?: string;
signerMode?: boolean;
signerFileSystemPath?: string;
lightningSignerFileSystemPath?: string;
}

export const ArgConfig = (args): Partial<Config> => ({
Expand All @@ -59,6 +60,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 +82,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 +105,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 Down Expand Up @@ -161,6 +164,7 @@ function mergeConfigs(...configs: Partial<Config>[]): Config {
externalSignerUrl,
signerMode: get('signerMode'),
signerFileSystemPath: get('signerFileSystemPath'),
lightningSignerFileSystemPath: get('lightningSignerFileSystemPath'),
};
}

Expand Down
6 changes: 6 additions & 0 deletions modules/express/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,9 @@ export class ExternalSignerConfigError extends BitGoJsError {
super(message || 'External signer configuration is invalid');
}
}

export class LightningSignerConfigError extends BitGoJsError {
public constructor(message?: string) {
super(message || 'Lightning signer configuration is invalid');
}
}
37 changes: 30 additions & 7 deletions modules/express/src/expressApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ import { Config, config } from './config';
const debug = debugLib('bitgo:express');

import { SSL_OP_NO_TLSv1 } from 'constants';
import { IpcError, NodeEnvironmentError, TlsConfigurationError, ExternalSignerConfigError } from './errors';
import {
IpcError,
NodeEnvironmentError,
TlsConfigurationError,
ExternalSignerConfigError,
LightningSignerConfigError,
} from './errors';

import { Environments } from 'bitgo';
import * as clientRoutes from './clientRoutes';
Expand Down Expand Up @@ -104,7 +110,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 +128,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 @@ -156,13 +165,13 @@ export function createBaseUri(config: Config): string {
}

/**
* Check the that the json file containing the external signer private key exists
* Check the that the json file exists
* @param path
*/
function checkSignerPrvPath(path: string) {
function checkJsonFilePath(path: string) {
try {
const privKeyFile = fs.readFileSync(path, { encoding: 'utf8' });
JSON.parse(privKeyFile);
const jsonFile = fs.readFileSync(path, { encoding: 'utf8' });
JSON.parse(jsonFile);
} catch (e) {
throw new Error(`Failed to parse ${path} - ${e.message}`);
}
Expand All @@ -186,6 +195,7 @@ function checkPreconditions(config: Config) {
externalSignerUrl,
signerMode,
signerFileSystemPath,
lightningSignerFileSystemPath,
} = config;

// warn or throw if the NODE_ENV is not production when BITGO_ENV is production - this can leak system info from express
Expand Down Expand Up @@ -229,15 +239,28 @@ function checkPreconditions(config: Config) {
);
}

if (signerMode !== undefined && lightningSignerFileSystemPath !== undefined) {
throw new LightningSignerConfigError(
'signerMode and lightningSignerFileSystemPath cannot be set at the same time.'
);
}

if (signerFileSystemPath !== undefined) {
checkSignerPrvPath(signerFileSystemPath);
checkJsonFilePath(signerFileSystemPath);
}

if (lightningSignerFileSystemPath !== undefined) {
checkJsonFilePath(lightningSignerFileSystemPath);
}
}

export function setupRoutes(app: express.Application, config: Config): void {
if (config.signerMode) {
clientRoutes.setupSigningRoutes(app, config);
} else {
if (config.lightningSignerFileSystemPath) {
clientRoutes.setupLightningRoutes(app, config);
}
clientRoutes.setupAPIRoutes(app, config);
}
}
Expand Down
77 changes: 77 additions & 0 deletions modules/express/src/lightning/codecs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import * as t from 'io-ts';
import { NonEmptyString } from 'io-ts-types';
import { IPCustomCodec } from '@bitgo/sdk-core';

export const WalletStateCodec = t.keyof({
NON_EXISTING: 1,
LOCKED: 1,
UNLOCKED: 1,
RPC_ACTIVE: 1,
SERVER_ACTIVE: 1,
WAITING_TO_START: 1,
});

export type WalletState = t.TypeOf<typeof WalletStateCodec>;

export const LightningSignerConfigCodec = t.type({
url: NonEmptyString,
tlsCert: NonEmptyString,
});

export type LightningSignerConfig = t.TypeOf<typeof LightningSignerConfigCodec>;

export const LightningSignerConfigsCodec = t.record(t.string, LightningSignerConfigCodec);

export type LightningSignerConfigs = t.TypeOf<typeof LightningSignerConfigsCodec>;

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

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

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

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

export const CreateSignerMacaroonRequestCodec = t.strict(
{
walletId: NonEmptyString,
passphrase: NonEmptyString,
watchOnlyIP: IPCustomCodec,
},
'CreateSignerMacaroonRequest'
);

export type CreateSignerMacaroonRequest = t.TypeOf<typeof CreateSignerMacaroonRequestCodec>;

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

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

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

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