PHP WebShell
Текущая директория: /opt/BitGoJS/modules/express/src/lightning
Просмотр файла: lightningSignerRoutes.ts
import { isIP } from 'net';
import * as express from 'express';
import { decodeOrElse } from '@bitgo/sdk-core';
import {
getUtxolibNetwork,
signerMacaroonPermissions,
createWatchOnly,
addIPCaveatToMacaroon,
isLightningCoinName,
getLightningKeychain,
getLightningAuthKeychains,
updateWalletCoinSpecific,
} from '@bitgo/abstract-lightning';
import * as utxolib from '@bitgo/utxo-lib';
import { Buffer } from 'buffer';
import {
CreateSignerMacaroonRequest,
GetWalletStateResponse,
InitLightningWalletRequest,
UnlockLightningWalletRequest,
} from './codecs';
import { LndSignerClient } from './lndSignerClient';
import { ApiResponseError } from '../errors';
type Decrypt = (params: { input: string; password: string }) => string;
async function createSignerMacaroon(
lndSignerClient: LndSignerClient,
header: { adminMacaroonHex: string },
watchOnlyIp: string | undefined | null
): Promise<string> {
const { macaroon } = await lndSignerClient.bakeMacaroon({ permissions: signerMacaroonPermissions }, header);
const macaroonBase64 = watchOnlyIp
? addIPCaveatToMacaroon(Buffer.from(macaroon, 'hex').toString('base64'), watchOnlyIp)
: undefined;
return macaroonBase64 ? Buffer.from(macaroonBase64, 'base64').toString('hex') : macaroon;
}
function getSignerRootKey(
passphrase: string,
userMainnetEncryptedPrv: string,
network: utxolib.Network,
decrypt: Decrypt
) {
const userMainnetPrv = decrypt({
password: passphrase,
input: userMainnetEncryptedPrv,
});
return utxolib.bitgo.keyutil.convertExtendedKeyNetwork(userMainnetPrv, utxolib.networks.bitcoin, network);
}
function getMacaroonRootKey(passphrase: string, nodeAuthEncryptedPrv: string, decrypt: Decrypt) {
const hdNode = utxolib.bip32.fromBase58(decrypt({ password: passphrase, input: nodeAuthEncryptedPrv }));
if (!hdNode.privateKey) {
throw new Error('nodeAuthEncryptedPrv is not a private key');
}
return hdNode.privateKey.toString('base64');
}
/**
* Handle the request to initialise remote signer LND for a wallet.
*/
export async function handleInitLightningWallet(req: express.Request): Promise<unknown> {
const bitgo = req.bitgo;
const coinName = req.params.coin;
if (!isLightningCoinName(coinName)) {
throw new ApiResponseError(`Invalid coin ${coinName}. This is not a lightning coin.`, 400);
}
const coin = bitgo.coin(coinName);
const walletId = req.params.id;
if (typeof walletId !== 'string') {
throw new ApiResponseError(`Invalid wallet id: ${walletId}`, 400);
}
const { passphrase, expressHost } = decodeOrElse(
InitLightningWalletRequest.name,
InitLightningWalletRequest,
req.body,
(_) => {
// DON'T throw errors from decodeOrElse. It could leak sensitive information.
throw new ApiResponseError('Invalid request body to initialize lightning wallet', 400);
}
);
const wallet = await coin.wallets().get({ id: walletId, includeBalance: false });
if (wallet.subType() !== 'lightningSelfCustody') {
throw new ApiResponseError(`not a self custodial lighting wallet ${walletId}`, 400);
}
const lndSignerClient = await LndSignerClient.create(walletId, req.config);
const userKey = await getLightningKeychain(wallet);
const userKeyEncryptedPrv = userKey.encryptedPrv;
if (!userKeyEncryptedPrv) {
throw new ApiResponseError('Missing encryptedPrv in user keychain', 400);
}
const { nodeAuthKey } = await getLightningAuthKeychains(wallet);
const nodeAuthKeyEncryptedPrv = nodeAuthKey.encryptedPrv;
if (!nodeAuthKeyEncryptedPrv) {
throw new ApiResponseError('Missing encryptedPrv in node auth keychain', 400);
}
const network = getUtxolibNetwork(coin.getChain());
const signerRootKey = getSignerRootKey(passphrase, userKeyEncryptedPrv, network, bitgo.decrypt);
const macaroonRootKey = getMacaroonRootKey(passphrase, nodeAuthKeyEncryptedPrv, bitgo.decrypt);
const { admin_macaroon: adminMacaroon } = await lndSignerClient.initWallet({
// The passphrase at LND can only accommodate a base64 character set
// For more information, see BTC-1851
wallet_password: Buffer.from(passphrase).toString('base64'),
extended_master_key: signerRootKey,
macaroon_root_key: macaroonRootKey,
});
return await updateWalletCoinSpecific(wallet, {
signerAdminMacaroon:
expressHost && !!isIP(expressHost) ? addIPCaveatToMacaroon(adminMacaroon, expressHost) : adminMacaroon,
watchOnlyAccounts: createWatchOnly(signerRootKey, network),
passphrase,
});
}
/**
* Handle the request to create a signer macaroon from remote signer LND for a wallet.
*/
export async function handleCreateSignerMacaroon(req: express.Request): Promise<unknown> {
const bitgo = req.bitgo;
const coinName = req.params.coin;
if (!isLightningCoinName(coinName)) {
throw new ApiResponseError(`Invalid coin to create signer macaroon: ${coinName}. Must be a lightning coin.`, 400);
}
const coin = bitgo.coin(coinName);
const walletId = req.params.id;
if (typeof walletId !== 'string') {
throw new ApiResponseError(`Invalid wallet id: ${walletId}`, 400);
}
const { passphrase, addIpCaveatToMacaroon } = decodeOrElse(
CreateSignerMacaroonRequest.name,
CreateSignerMacaroonRequest,
req.body,
(_) => {
// DON'T throw errors from decodeOrElse. It could leak sensitive information.
throw new ApiResponseError('Invalid request body to create signer macaroon', 400);
}
);
const wallet = await coin.wallets().get({ id: walletId, includeBalance: false });
if (wallet.subType() !== 'lightningSelfCustody') {
throw new ApiResponseError(`not a self custodial lighting wallet ${walletId}`, 400);
}
const watchOnlyIp = wallet.coinSpecific()?.watchOnlyExternalIp;
if (!watchOnlyIp && addIpCaveatToMacaroon) {
throw new ApiResponseError(
'Cannot create signer macaroon because the external IP is not set. This can take some time. Contact support@bitgo.com if longer than 24 hours.',
400
);
}
if (watchOnlyIp && !isIP(watchOnlyIp)) {
throw new ApiResponseError(`Invalid IP address: ${watchOnlyIp}. Contact support@bitgo.com`, 500);
}
const lndSignerClient = await LndSignerClient.create(walletId, req.config);
const encryptedSignerAdminMacaroon = wallet.coinSpecific()?.encryptedSignerAdminMacaroon;
if (!encryptedSignerAdminMacaroon) {
throw new ApiResponseError('Missing encryptedSignerAdminMacaroon in wallet', 400);
}
const adminMacaroon = bitgo.decrypt({
password: passphrase,
input: encryptedSignerAdminMacaroon,
});
const signerMacaroon = await createSignerMacaroon(
lndSignerClient,
{ adminMacaroonHex: Buffer.from(adminMacaroon, 'base64').toString('hex') },
addIpCaveatToMacaroon ? watchOnlyIp : null
);
return await updateWalletCoinSpecific(wallet, {
signerMacaroon,
passphrase,
});
}
/**
* Handle the request to get the state of a wallet from the signer.
*/
export async function handleGetLightningWalletState(req: express.Request): Promise<GetWalletStateResponse> {
const coinName = req.params.coin;
if (!isLightningCoinName(coinName)) {
throw new ApiResponseError(`Invalid coin to get lightning wallet state: ${coinName}`, 400);
}
const walletId = req.params.id;
if (typeof walletId !== 'string') {
throw new ApiResponseError(`Invalid wallet id: ${walletId}`, 400);
}
const lndSignerClient = await LndSignerClient.create(walletId, req.config);
return await lndSignerClient.getWalletState();
}
/**
* Handle the request to unlock a wallet in the signer.
*/
export async function handleUnlockLightningWallet(req: express.Request): Promise<{ message: string }> {
const coinName = req.params.coin;
if (!isLightningCoinName(coinName)) {
throw new ApiResponseError(`Invalid coin to unlock lightning wallet: ${coinName}`, 400);
}
const walletId = req.params.id;
if (typeof walletId !== 'string') {
throw new ApiResponseError(`Invalid wallet id: ${walletId}`, 400);
}
const { passphrase } = decodeOrElse(
UnlockLightningWalletRequest.name,
UnlockLightningWalletRequest,
req.body,
(_) => {
// DON'T throw errors from decodeOrElse. It could leak sensitive information.
throw new ApiResponseError('Invalid request body to unlock lightning wallet', 400);
}
);
const lndSignerClient = await LndSignerClient.create(walletId, req.config);
// The passphrase at LND can only accommodate a base64 character set
// For more information, see BTC-1851
await lndSignerClient.unlockWallet({
wallet_password: Buffer.from(passphrase).toString('base64'),
});
return { message: 'ok' };
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!