PHP WebShell
Текущая директория: /opt/BitGoJS/modules/abstract-lightning/src/lightning
Просмотр файла: lightningUtils.ts
import * as statics from '@bitgo/statics';
import * as utxolib from '@bitgo/utxo-lib';
import { importMacaroon, bytesToBase64 } from 'macaroon';
import * as bs58check from 'bs58check';
import * as sdkcore from '@bitgo/sdk-core';
import { WatchOnly, WatchOnlyAccount } from '../codecs';
// https://github.com/lightningnetwork/lnd/blob/master/docs/remote-signing.md#the-signer-node
export const signerMacaroonPermissions = [
{
entity: 'message',
action: 'write',
},
{
entity: 'signer',
action: 'generate',
},
{
entity: 'address',
action: 'read',
},
{
entity: 'onchain',
action: 'write',
},
];
export const lightningNetworkName = ['bitcoin', 'testnet'] as const;
export type LightningNetworkName = (typeof lightningNetworkName)[number];
/**
* Checks if the coin name is a lightning coin name.
*/
export function isLightningCoinName(coinName: unknown): coinName is 'lnbtc' | 'tlnbtc' {
return coinName === 'lnbtc' || coinName === 'tlnbtc';
}
/**
* Get the utxolib network for a lightning network.
*/
export function getLightningNetwork(networkName: LightningNetworkName): utxolib.Network {
return utxolib.networks[networkName];
}
/**
* Get the lightning coin name for a utxolib network.
*/
export function getLightningCoinName(network: utxolib.Network): string {
return network === utxolib.networks.bitcoin ? 'lnbtc' : 'tlnbtc';
}
/**
* Checks if the network name is a valid lightning network name.
*/
export function isValidLightningNetworkName(networkName: unknown): networkName is LightningNetworkName {
return lightningNetworkName.includes(networkName as LightningNetworkName);
}
/**
* Checks if the network is a valid lightning network.
*/
export function isValidLightningNetwork(network: unknown): network is utxolib.Network {
return utxolib.isValidNetwork(network) && isValidLightningNetworkName(utxolib.getNetworkName(network));
}
/**
* Returns the statics network data for a lightning coin.
*/
export function getStaticsLightningNetwork(coinName: string): statics.LightningNetwork {
if (!isLightningCoinName(coinName)) {
throw new Error(`${coinName} is not a lightning coin`);
}
const coin = statics.coins.get(coinName);
if (!(coin instanceof statics.LightningCoin)) {
throw new Error('coin is not a lightning coin');
}
return coin.network;
}
/**
* Returns the utxolib network for a lightning coin.
*/
export function getUtxolibNetwork(coinName: string): utxolib.Network {
const networkName = getStaticsLightningNetwork(coinName).utxolibName;
if (!isValidLightningNetworkName(networkName)) {
throw new Error('invalid lightning network');
}
return getLightningNetwork(networkName);
}
/**
* Returns coin specific data for a lightning coin.
*/
export function unwrapLightningCoinSpecific<V>(obj: { lnbtc: V } | { tlnbtc: V }, coinSpecificPath: string): V {
if (coinSpecificPath !== 'lnbtc' && coinSpecificPath !== 'tlnbtc') {
throw new Error(`invalid coinSpecificPath ${coinSpecificPath} for lightning coin`);
}
if (coinSpecificPath === 'lnbtc' && 'lnbtc' in obj) {
return obj.lnbtc;
}
if (coinSpecificPath === 'tlnbtc' && 'tlnbtc' in obj) {
return obj.tlnbtc;
}
throw new Error('invalid lightning coin specific');
}
/**
* Adds an IP caveat to a macaroon and returns the modified macaroon as a Base64 string.
*/
export function addIPCaveatToMacaroon(macaroonBase64: string, ip: string): string {
const macaroon = importMacaroon(macaroonBase64);
macaroon.addFirstPartyCaveat(`ipaddr ${ip}`);
return bytesToBase64(macaroon.exportBinary());
}
const PURPOSE_WRAPPED_P2WKH = 49;
const PURPOSE_P2WKH = 84;
const PURPOSE_P2TR = 86;
const PURPOSE_ALL_OTHERS = 1017;
type ExtendedKeyPurpose =
| typeof PURPOSE_WRAPPED_P2WKH
| typeof PURPOSE_P2WKH
| typeof PURPOSE_P2TR
| typeof PURPOSE_ALL_OTHERS;
/**
* Converts an extended public key (xpub) to the appropriate prefix (ypub, vpub, etc.) based on its purpose and network.
*/
function convertXpubPrefix(xpub: string, purpose: ExtendedKeyPurpose, isMainnet: boolean): string {
if (purpose === PURPOSE_P2TR || purpose === PURPOSE_ALL_OTHERS) {
return xpub;
}
const data = bs58check.decode(xpub);
let versionBytes: Buffer;
switch (purpose) {
case PURPOSE_WRAPPED_P2WKH:
versionBytes = isMainnet ? Buffer.from([0x04, 0x9d, 0x7c, 0xb2]) : Buffer.from([0x04, 0x4a, 0x52, 0x62]); // ypub/upub for p2sh-p2wpkh
break;
case PURPOSE_P2WKH:
versionBytes = isMainnet ? Buffer.from([0x04, 0xb2, 0x47, 0x46]) : Buffer.from([0x04, 0x5f, 0x1c, 0xf6]); // zpub/vpub for p2wpkh
break;
default:
throw new Error('Unsupported purpose');
}
versionBytes.copy(data, 0, 0, 4);
return bs58check.encode(data);
}
/**
* Derives watch-only accounts from the master HD node for the given purposes and network.
*/
function deriveWatchOnlyAccounts(masterHDNode: utxolib.BIP32Interface, isMainnet: boolean): WatchOnlyAccount[] {
// https://github.com/lightningnetwork/lnd/blob/master/docs/remote-signing.md#required-accounts
if (masterHDNode.isNeutered()) {
throw new Error('masterHDNode must not be neutered');
}
const purposes = [PURPOSE_WRAPPED_P2WKH, PURPOSE_P2WKH, PURPOSE_P2TR, PURPOSE_ALL_OTHERS] as const;
return purposes.flatMap((purpose) => {
const maxAccount = purpose === PURPOSE_ALL_OTHERS ? 255 : 0;
const coinType = purpose !== PURPOSE_ALL_OTHERS || isMainnet ? 0 : 1;
return Array.from({ length: maxAccount + 1 }, (_, account) => {
const path = `m/${purpose}'/${coinType}'/${account}'`;
const derivedNode = masterHDNode.derivePath(path);
// Ensure the node is neutered (i.e., converted to public key only)
const neuteredNode = derivedNode.neutered();
const xpub = convertXpubPrefix(neuteredNode.toBase58(), purpose, isMainnet);
return {
purpose,
coin_type: coinType,
account,
xpub,
};
});
});
}
/**
* Creates a watch-only wallet init data from the provided signer root key and network.
*/
export function createWatchOnly(signerRootKey: string, network: utxolib.Network): WatchOnly {
const masterHDNode = utxolib.bip32.fromBase58(signerRootKey, network);
const getCurrentUnixTimestamp = () => {
return Math.floor(Date.now() / 1000);
};
const master_key_birthday_timestamp = getCurrentUnixTimestamp().toString();
const master_key_fingerprint = masterHDNode.fingerprint.toString('hex');
const accounts = deriveWatchOnlyAccounts(masterHDNode, utxolib.isMainnet(network));
return { master_key_birthday_timestamp, master_key_fingerprint, accounts };
}
/**
* Derives the shared Elliptic Curve Diffie-Hellman (ECDH) secret between the user's auth extended private key
* and the Lightning service's public key for secure communication.
*/
export function deriveLightningServiceSharedSecret(coinName: 'lnbtc' | 'tlnbtc', userAuthXprv: string): Buffer {
const publicKey = Buffer.from(getStaticsLightningNetwork(coinName).lightningServicePubKey, 'hex');
const userAuthHdNode = utxolib.bip32.fromBase58(userAuthXprv);
return sdkcore.getSharedSecret(userAuthHdNode, publicKey);
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!