diff --git a/datasource.js b/datasource.js index c0b2b318..9691f3aa 100644 --- a/datasource.js +++ b/datasource.js @@ -12,14 +12,17 @@ import { UserReceivingAddress } from "./build/src/services/storage/entity/UserRe import { UserToUserPayment } from "./build/src/services/storage/entity/UserToUserPayment.js" import { UserTransactionPayment } from "./build/src/services/storage/entity/UserTransactionPayment.js" import { LspOrder } from "./build/src/services/storage/entity/LspOrder.js" +import { LndNodeInfo } from "./build/src/services/storage/entity/LndNodeInfo.js" import { Initial1703170309875 } from './build/src/services/storage/migrations/1703170309875-initial.js' import { LspOrder1718387847693 } from './build/src/services/storage/migrations/1718387847693-lsp_order.js' +import { LiquidityProvider1719335699480 } from './build/src/services/storage/migrations/1719335699480-liquidity_provider.js' export default new DataSource({ type: "sqlite", database: "db.sqlite", // logging: true, - migrations: [Initial1703170309875, LspOrder1718387847693], + migrations: [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480], entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment, - UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, LspOrder], + UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, LspOrder, LndNodeInfo], // synchronize: true, -}) \ No newline at end of file +}) +//npx typeorm migration:generate ./src/services/storage/migrations/lnd_node_info -d ./datasource.js \ No newline at end of file diff --git a/proto/lnd/walletunlocker.client.ts b/proto/lnd/walletunlocker.client.ts new file mode 100644 index 00000000..a3e3d7da --- /dev/null +++ b/proto/lnd/walletunlocker.client.ts @@ -0,0 +1,180 @@ +// @generated by protobuf-ts 2.8.1 +// @generated from protobuf file "walletunlocker.proto" (package "lnrpc", syntax proto3) +// tslint:disable +import type { RpcTransport } from "@protobuf-ts/runtime-rpc"; +import type { ServiceInfo } from "@protobuf-ts/runtime-rpc"; +import { WalletUnlocker } from "./walletunlocker.js"; +import type { ChangePasswordResponse } from "./walletunlocker.js"; +import type { ChangePasswordRequest } from "./walletunlocker.js"; +import type { UnlockWalletResponse } from "./walletunlocker.js"; +import type { UnlockWalletRequest } from "./walletunlocker.js"; +import type { InitWalletResponse } from "./walletunlocker.js"; +import type { InitWalletRequest } from "./walletunlocker.js"; +import { stackIntercept } from "@protobuf-ts/runtime-rpc"; +import type { GenSeedResponse } from "./walletunlocker.js"; +import type { GenSeedRequest } from "./walletunlocker.js"; +import type { UnaryCall } from "@protobuf-ts/runtime-rpc"; +import type { RpcOptions } from "@protobuf-ts/runtime-rpc"; +// +// Comments in this file will be directly parsed into the API +// Documentation as descriptions of the associated method, message, or field. +// These descriptions should go right above the definition of the object, and +// can be in either block or // comment format. +// +// An RPC method can be matched to an lncli command by placing a line in the +// beginning of the description in exactly the following format: +// lncli: `methodname` +// +// Failure to specify the exact name of the command will cause documentation +// generation to fail. +// +// More information on how exactly the gRPC documentation is generated from +// this proto file can be found here: +// https://github.com/lightninglabs/lightning-api + +/** + * WalletUnlocker is a service that is used to set up a wallet password for + * lnd at first startup, and unlock a previously set up wallet. + * + * @generated from protobuf service lnrpc.WalletUnlocker + */ +export interface IWalletUnlockerClient { + /** + * + * GenSeed is the first method that should be used to instantiate a new lnd + * instance. This method allows a caller to generate a new aezeed cipher seed + * given an optional passphrase. If provided, the passphrase will be necessary + * to decrypt the cipherseed to expose the internal wallet seed. + * + * Once the cipherseed is obtained and verified by the user, the InitWallet + * method should be used to commit the newly generated seed, and create the + * wallet. + * + * @generated from protobuf rpc: GenSeed(lnrpc.GenSeedRequest) returns (lnrpc.GenSeedResponse); + */ + genSeed(input: GenSeedRequest, options?: RpcOptions): UnaryCall; + /** + * + * InitWallet is used when lnd is starting up for the first time to fully + * initialize the daemon and its internal wallet. At the very least a wallet + * password must be provided. This will be used to encrypt sensitive material + * on disk. + * + * In the case of a recovery scenario, the user can also specify their aezeed + * mnemonic and passphrase. If set, then the daemon will use this prior state + * to initialize its internal wallet. + * + * Alternatively, this can be used along with the GenSeed RPC to obtain a + * seed, then present it to the user. Once it has been verified by the user, + * the seed can be fed into this RPC in order to commit the new wallet. + * + * @generated from protobuf rpc: InitWallet(lnrpc.InitWalletRequest) returns (lnrpc.InitWalletResponse); + */ + initWallet(input: InitWalletRequest, options?: RpcOptions): UnaryCall; + /** + * lncli: `unlock` + * UnlockWallet is used at startup of lnd to provide a password to unlock + * the wallet database. + * + * @generated from protobuf rpc: UnlockWallet(lnrpc.UnlockWalletRequest) returns (lnrpc.UnlockWalletResponse); + */ + unlockWallet(input: UnlockWalletRequest, options?: RpcOptions): UnaryCall; + /** + * lncli: `changepassword` + * ChangePassword changes the password of the encrypted wallet. This will + * automatically unlock the wallet database if successful. + * + * @generated from protobuf rpc: ChangePassword(lnrpc.ChangePasswordRequest) returns (lnrpc.ChangePasswordResponse); + */ + changePassword(input: ChangePasswordRequest, options?: RpcOptions): UnaryCall; +} +// +// Comments in this file will be directly parsed into the API +// Documentation as descriptions of the associated method, message, or field. +// These descriptions should go right above the definition of the object, and +// can be in either block or // comment format. +// +// An RPC method can be matched to an lncli command by placing a line in the +// beginning of the description in exactly the following format: +// lncli: `methodname` +// +// Failure to specify the exact name of the command will cause documentation +// generation to fail. +// +// More information on how exactly the gRPC documentation is generated from +// this proto file can be found here: +// https://github.com/lightninglabs/lightning-api + +/** + * WalletUnlocker is a service that is used to set up a wallet password for + * lnd at first startup, and unlock a previously set up wallet. + * + * @generated from protobuf service lnrpc.WalletUnlocker + */ +export class WalletUnlockerClient implements IWalletUnlockerClient, ServiceInfo { + typeName = WalletUnlocker.typeName; + methods = WalletUnlocker.methods; + options = WalletUnlocker.options; + constructor(private readonly _transport: RpcTransport) { + } + /** + * + * GenSeed is the first method that should be used to instantiate a new lnd + * instance. This method allows a caller to generate a new aezeed cipher seed + * given an optional passphrase. If provided, the passphrase will be necessary + * to decrypt the cipherseed to expose the internal wallet seed. + * + * Once the cipherseed is obtained and verified by the user, the InitWallet + * method should be used to commit the newly generated seed, and create the + * wallet. + * + * @generated from protobuf rpc: GenSeed(lnrpc.GenSeedRequest) returns (lnrpc.GenSeedResponse); + */ + genSeed(input: GenSeedRequest, options?: RpcOptions): UnaryCall { + const method = this.methods[0], opt = this._transport.mergeOptions(options); + return stackIntercept("unary", this._transport, method, opt, input); + } + /** + * + * InitWallet is used when lnd is starting up for the first time to fully + * initialize the daemon and its internal wallet. At the very least a wallet + * password must be provided. This will be used to encrypt sensitive material + * on disk. + * + * In the case of a recovery scenario, the user can also specify their aezeed + * mnemonic and passphrase. If set, then the daemon will use this prior state + * to initialize its internal wallet. + * + * Alternatively, this can be used along with the GenSeed RPC to obtain a + * seed, then present it to the user. Once it has been verified by the user, + * the seed can be fed into this RPC in order to commit the new wallet. + * + * @generated from protobuf rpc: InitWallet(lnrpc.InitWalletRequest) returns (lnrpc.InitWalletResponse); + */ + initWallet(input: InitWalletRequest, options?: RpcOptions): UnaryCall { + const method = this.methods[1], opt = this._transport.mergeOptions(options); + return stackIntercept("unary", this._transport, method, opt, input); + } + /** + * lncli: `unlock` + * UnlockWallet is used at startup of lnd to provide a password to unlock + * the wallet database. + * + * @generated from protobuf rpc: UnlockWallet(lnrpc.UnlockWalletRequest) returns (lnrpc.UnlockWalletResponse); + */ + unlockWallet(input: UnlockWalletRequest, options?: RpcOptions): UnaryCall { + const method = this.methods[2], opt = this._transport.mergeOptions(options); + return stackIntercept("unary", this._transport, method, opt, input); + } + /** + * lncli: `changepassword` + * ChangePassword changes the password of the encrypted wallet. This will + * automatically unlock the wallet database if successful. + * + * @generated from protobuf rpc: ChangePassword(lnrpc.ChangePasswordRequest) returns (lnrpc.ChangePasswordResponse); + */ + changePassword(input: ChangePasswordRequest, options?: RpcOptions): UnaryCall { + const method = this.methods[3], opt = this._transport.mergeOptions(options); + return stackIntercept("unary", this._transport, method, opt, input); + } +} diff --git a/proto/lnd/walletunlocker.ts b/proto/lnd/walletunlocker.ts new file mode 100644 index 00000000..a472c857 --- /dev/null +++ b/proto/lnd/walletunlocker.ts @@ -0,0 +1,991 @@ +// @generated by protobuf-ts 2.8.1 +// @generated from protobuf file "walletunlocker.proto" (package "lnrpc", syntax proto3) +// tslint:disable +import { ServiceType } from "@protobuf-ts/runtime-rpc"; +import type { BinaryWriteOptions } from "@protobuf-ts/runtime"; +import type { IBinaryWriter } from "@protobuf-ts/runtime"; +import { WireType } from "@protobuf-ts/runtime"; +import type { BinaryReadOptions } from "@protobuf-ts/runtime"; +import type { IBinaryReader } from "@protobuf-ts/runtime"; +import { UnknownFieldHandler } from "@protobuf-ts/runtime"; +import type { PartialMessage } from "@protobuf-ts/runtime"; +import { reflectionMergePartial } from "@protobuf-ts/runtime"; +import { MESSAGE_TYPE } from "@protobuf-ts/runtime"; +import { MessageType } from "@protobuf-ts/runtime"; +import { ChanBackupSnapshot } from "./lightning.js"; +/** + * @generated from protobuf message lnrpc.GenSeedRequest + */ +export interface GenSeedRequest { + /** + * + * aezeed_passphrase is an optional user provided passphrase that will be used + * to encrypt the generated aezeed cipher seed. When using REST, this field + * must be encoded as base64. + * + * @generated from protobuf field: bytes aezeed_passphrase = 1; + */ + aezeedPassphrase: Uint8Array; + /** + * + * seed_entropy is an optional 16-bytes generated via CSPRNG. If not + * specified, then a fresh set of randomness will be used to create the seed. + * When using REST, this field must be encoded as base64. + * + * @generated from protobuf field: bytes seed_entropy = 2; + */ + seedEntropy: Uint8Array; +} +/** + * @generated from protobuf message lnrpc.GenSeedResponse + */ +export interface GenSeedResponse { + /** + * + * cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed + * cipher seed obtained by the user. This field is optional, as if not + * provided, then the daemon will generate a new cipher seed for the user. + * Otherwise, then the daemon will attempt to recover the wallet state linked + * to this cipher seed. + * + * @generated from protobuf field: repeated string cipher_seed_mnemonic = 1; + */ + cipherSeedMnemonic: string[]; + /** + * + * enciphered_seed are the raw aezeed cipher seed bytes. This is the raw + * cipher text before run through our mnemonic encoding scheme. + * + * @generated from protobuf field: bytes enciphered_seed = 2; + */ + encipheredSeed: Uint8Array; +} +/** + * @generated from protobuf message lnrpc.InitWalletRequest + */ +export interface InitWalletRequest { + /** + * + * wallet_password is the passphrase that should be used to encrypt the + * wallet. This MUST be at least 8 chars in length. After creation, this + * password is required to unlock the daemon. When using REST, this field + * must be encoded as base64. + * + * @generated from protobuf field: bytes wallet_password = 1; + */ + walletPassword: Uint8Array; + /** + * + * cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed + * cipher seed obtained by the user. This may have been generated by the + * GenSeed method, or be an existing seed. + * + * @generated from protobuf field: repeated string cipher_seed_mnemonic = 2; + */ + cipherSeedMnemonic: string[]; + /** + * + * aezeed_passphrase is an optional user provided passphrase that will be used + * to encrypt the generated aezeed cipher seed. When using REST, this field + * must be encoded as base64. + * + * @generated from protobuf field: bytes aezeed_passphrase = 3; + */ + aezeedPassphrase: Uint8Array; + /** + * + * recovery_window is an optional argument specifying the address lookahead + * when restoring a wallet seed. The recovery window applies to each + * individual branch of the BIP44 derivation paths. Supplying a recovery + * window of zero indicates that no addresses should be recovered, such after + * the first initialization of the wallet. + * + * @generated from protobuf field: int32 recovery_window = 4; + */ + recoveryWindow: number; + /** + * + * channel_backups is an optional argument that allows clients to recover the + * settled funds within a set of channels. This should be populated if the + * user was unable to close out all channels and sweep funds before partial or + * total data loss occurred. If specified, then after on-chain recovery of + * funds, lnd begin to carry out the data loss recovery protocol in order to + * recover the funds in each channel from a remote force closed transaction. + * + * @generated from protobuf field: lnrpc.ChanBackupSnapshot channel_backups = 5; + */ + channelBackups?: ChanBackupSnapshot; + /** + * + * stateless_init is an optional argument instructing the daemon NOT to create + * any *.macaroon files in its filesystem. If this parameter is set, then the + * admin macaroon returned in the response MUST be stored by the caller of the + * RPC as otherwise all access to the daemon will be lost! + * + * @generated from protobuf field: bool stateless_init = 6; + */ + statelessInit: boolean; + /** + * + * extended_master_key is an alternative to specifying cipher_seed_mnemonic and + * aezeed_passphrase. Instead of deriving the master root key from the entropy + * of an aezeed cipher seed, the given extended master root key is used + * directly as the wallet's master key. This allows users to import/use a + * master key from another wallet. When doing so, lnd still uses its default + * SegWit only (BIP49/84) derivation paths and funds from custom/non-default + * derivation paths will not automatically appear in the on-chain wallet. Using + * an 'xprv' instead of an aezeed also has the disadvantage that the wallet's + * birthday is not known as that is an information that's only encoded in the + * aezeed, not the xprv. Therefore a birthday needs to be specified in + * extended_master_key_birthday_timestamp or a "safe" default value will be + * used. + * + * @generated from protobuf field: string extended_master_key = 7; + */ + extendedMasterKey: string; + /** + * + * extended_master_key_birthday_timestamp is the optional unix timestamp in + * seconds to use as the wallet's birthday when using an extended master key + * to restore the wallet. lnd will only start scanning for funds in blocks that + * are after the birthday which can speed up the process significantly. If the + * birthday is not known, this should be left at its default value of 0 in + * which case lnd will start scanning from the first SegWit block (481824 on + * mainnet). + * + * @generated from protobuf field: uint64 extended_master_key_birthday_timestamp = 8; + */ + extendedMasterKeyBirthdayTimestamp: bigint; + /** + * + * watch_only is the third option of initializing a wallet: by importing + * account xpubs only and therefore creating a watch-only wallet that does not + * contain any private keys. That means the wallet won't be able to sign for + * any of the keys and _needs_ to be run with a remote signer that has the + * corresponding private keys and can serve signing RPC requests. + * + * @generated from protobuf field: lnrpc.WatchOnly watch_only = 9; + */ + watchOnly?: WatchOnly; + /** + * + * macaroon_root_key is an optional 32 byte macaroon root key that can be + * provided when initializing the wallet rather than letting lnd generate one + * on its own. + * + * @generated from protobuf field: bytes macaroon_root_key = 10; + */ + macaroonRootKey: Uint8Array; +} +/** + * @generated from protobuf message lnrpc.InitWalletResponse + */ +export interface InitWalletResponse { + /** + * + * The binary serialized admin macaroon that can be used to access the daemon + * after creating the wallet. If the stateless_init parameter was set to true, + * this is the ONLY copy of the macaroon and MUST be stored safely by the + * caller. Otherwise a copy of this macaroon is also persisted on disk by the + * daemon, together with other macaroon files. + * + * @generated from protobuf field: bytes admin_macaroon = 1; + */ + adminMacaroon: Uint8Array; +} +/** + * @generated from protobuf message lnrpc.WatchOnly + */ +export interface WatchOnly { + /** + * + * The unix timestamp in seconds of when the master key was created. lnd will + * only start scanning for funds in blocks that are after the birthday which + * can speed up the process significantly. If the birthday is not known, this + * should be left at its default value of 0 in which case lnd will start + * scanning from the first SegWit block (481824 on mainnet). + * + * @generated from protobuf field: uint64 master_key_birthday_timestamp = 1; + */ + masterKeyBirthdayTimestamp: bigint; + /** + * + * The fingerprint of the root key (also known as the key with derivation path + * m/) from which the account public keys were derived from. This may be + * required by some hardware wallets for proper identification and signing. The + * bytes must be in big-endian order. + * + * @generated from protobuf field: bytes master_key_fingerprint = 2; + */ + masterKeyFingerprint: Uint8Array; + /** + * + * The list of accounts to import. There _must_ be an account for all of lnd's + * main key scopes: BIP49/BIP84 (m/49'/0'/0', m/84'/0'/0', note that the + * coin type is always 0, even for testnet/regtest) and lnd's internal key + * scope (m/1017'/'/'), where account is the key family as + * defined in `keychain/derivation.go` (currently indices 0 to 9). + * + * @generated from protobuf field: repeated lnrpc.WatchOnlyAccount accounts = 3; + */ + accounts: WatchOnlyAccount[]; +} +/** + * @generated from protobuf message lnrpc.WatchOnlyAccount + */ +export interface WatchOnlyAccount { + /** + * + * Purpose is the first number in the derivation path, must be either 49, 84 + * or 1017. + * + * @generated from protobuf field: uint32 purpose = 1; + */ + purpose: number; + /** + * + * Coin type is the second number in the derivation path, this is _always_ 0 + * for purposes 49 and 84. It only needs to be set to 1 for purpose 1017 on + * testnet or regtest. + * + * @generated from protobuf field: uint32 coin_type = 2; + */ + coinType: number; + /** + * + * Account is the third number in the derivation path. For purposes 49 and 84 + * at least the default account (index 0) needs to be created but optional + * additional accounts are allowed. For purpose 1017 there needs to be exactly + * one account for each of the key families defined in `keychain/derivation.go` + * (currently indices 0 to 9) + * + * @generated from protobuf field: uint32 account = 3; + */ + account: number; + /** + * + * The extended public key at depth 3 for the given account. + * + * @generated from protobuf field: string xpub = 4; + */ + xpub: string; +} +/** + * @generated from protobuf message lnrpc.UnlockWalletRequest + */ +export interface UnlockWalletRequest { + /** + * + * wallet_password should be the current valid passphrase for the daemon. This + * will be required to decrypt on-disk material that the daemon requires to + * function properly. When using REST, this field must be encoded as base64. + * + * @generated from protobuf field: bytes wallet_password = 1; + */ + walletPassword: Uint8Array; + /** + * + * recovery_window is an optional argument specifying the address lookahead + * when restoring a wallet seed. The recovery window applies to each + * individual branch of the BIP44 derivation paths. Supplying a recovery + * window of zero indicates that no addresses should be recovered, such after + * the first initialization of the wallet. + * + * @generated from protobuf field: int32 recovery_window = 2; + */ + recoveryWindow: number; + /** + * + * channel_backups is an optional argument that allows clients to recover the + * settled funds within a set of channels. This should be populated if the + * user was unable to close out all channels and sweep funds before partial or + * total data loss occurred. If specified, then after on-chain recovery of + * funds, lnd begin to carry out the data loss recovery protocol in order to + * recover the funds in each channel from a remote force closed transaction. + * + * @generated from protobuf field: lnrpc.ChanBackupSnapshot channel_backups = 3; + */ + channelBackups?: ChanBackupSnapshot; + /** + * + * stateless_init is an optional argument instructing the daemon NOT to create + * any *.macaroon files in its file system. + * + * @generated from protobuf field: bool stateless_init = 4; + */ + statelessInit: boolean; +} +/** + * @generated from protobuf message lnrpc.UnlockWalletResponse + */ +export interface UnlockWalletResponse { +} +/** + * @generated from protobuf message lnrpc.ChangePasswordRequest + */ +export interface ChangePasswordRequest { + /** + * + * current_password should be the current valid passphrase used to unlock the + * daemon. When using REST, this field must be encoded as base64. + * + * @generated from protobuf field: bytes current_password = 1; + */ + currentPassword: Uint8Array; + /** + * + * new_password should be the new passphrase that will be needed to unlock the + * daemon. When using REST, this field must be encoded as base64. + * + * @generated from protobuf field: bytes new_password = 2; + */ + newPassword: Uint8Array; + /** + * + * stateless_init is an optional argument instructing the daemon NOT to create + * any *.macaroon files in its filesystem. If this parameter is set, then the + * admin macaroon returned in the response MUST be stored by the caller of the + * RPC as otherwise all access to the daemon will be lost! + * + * @generated from protobuf field: bool stateless_init = 3; + */ + statelessInit: boolean; + /** + * + * new_macaroon_root_key is an optional argument instructing the daemon to + * rotate the macaroon root key when set to true. This will invalidate all + * previously generated macaroons. + * + * @generated from protobuf field: bool new_macaroon_root_key = 4; + */ + newMacaroonRootKey: boolean; +} +/** + * @generated from protobuf message lnrpc.ChangePasswordResponse + */ +export interface ChangePasswordResponse { + /** + * + * The binary serialized admin macaroon that can be used to access the daemon + * after rotating the macaroon root key. If both the stateless_init and + * new_macaroon_root_key parameter were set to true, this is the ONLY copy of + * the macaroon that was created from the new root key and MUST be stored + * safely by the caller. Otherwise a copy of this macaroon is also persisted on + * disk by the daemon, together with other macaroon files. + * + * @generated from protobuf field: bytes admin_macaroon = 1; + */ + adminMacaroon: Uint8Array; +} +// @generated message type with reflection information, may provide speed optimized methods +class GenSeedRequest$Type extends MessageType { + constructor() { + super("lnrpc.GenSeedRequest", [ + { no: 1, name: "aezeed_passphrase", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, + { no: 2, name: "seed_entropy", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } + ]); + } + create(value?: PartialMessage): GenSeedRequest { + const message = { aezeedPassphrase: new Uint8Array(0), seedEntropy: new Uint8Array(0) }; + globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this }); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GenSeedRequest): GenSeedRequest { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* bytes aezeed_passphrase */ 1: + message.aezeedPassphrase = reader.bytes(); + break; + case /* bytes seed_entropy */ 2: + message.seedEntropy = reader.bytes(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: GenSeedRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* bytes aezeed_passphrase = 1; */ + if (message.aezeedPassphrase.length) + writer.tag(1, WireType.LengthDelimited).bytes(message.aezeedPassphrase); + /* bytes seed_entropy = 2; */ + if (message.seedEntropy.length) + writer.tag(2, WireType.LengthDelimited).bytes(message.seedEntropy); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message lnrpc.GenSeedRequest + */ +export const GenSeedRequest = new GenSeedRequest$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class GenSeedResponse$Type extends MessageType { + constructor() { + super("lnrpc.GenSeedResponse", [ + { no: 1, name: "cipher_seed_mnemonic", kind: "scalar", repeat: 2 /*RepeatType.UNPACKED*/, T: 9 /*ScalarType.STRING*/ }, + { no: 2, name: "enciphered_seed", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } + ]); + } + create(value?: PartialMessage): GenSeedResponse { + const message = { cipherSeedMnemonic: [], encipheredSeed: new Uint8Array(0) }; + globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this }); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GenSeedResponse): GenSeedResponse { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* repeated string cipher_seed_mnemonic */ 1: + message.cipherSeedMnemonic.push(reader.string()); + break; + case /* bytes enciphered_seed */ 2: + message.encipheredSeed = reader.bytes(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: GenSeedResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* repeated string cipher_seed_mnemonic = 1; */ + for (let i = 0; i < message.cipherSeedMnemonic.length; i++) + writer.tag(1, WireType.LengthDelimited).string(message.cipherSeedMnemonic[i]); + /* bytes enciphered_seed = 2; */ + if (message.encipheredSeed.length) + writer.tag(2, WireType.LengthDelimited).bytes(message.encipheredSeed); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message lnrpc.GenSeedResponse + */ +export const GenSeedResponse = new GenSeedResponse$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class InitWalletRequest$Type extends MessageType { + constructor() { + super("lnrpc.InitWalletRequest", [ + { no: 1, name: "wallet_password", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, + { no: 2, name: "cipher_seed_mnemonic", kind: "scalar", repeat: 2 /*RepeatType.UNPACKED*/, T: 9 /*ScalarType.STRING*/ }, + { no: 3, name: "aezeed_passphrase", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, + { no: 4, name: "recovery_window", kind: "scalar", T: 5 /*ScalarType.INT32*/ }, + { no: 5, name: "channel_backups", kind: "message", T: () => ChanBackupSnapshot }, + { no: 6, name: "stateless_init", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }, + { no: 7, name: "extended_master_key", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 8, name: "extended_master_key_birthday_timestamp", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 9, name: "watch_only", kind: "message", T: () => WatchOnly }, + { no: 10, name: "macaroon_root_key", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } + ]); + } + create(value?: PartialMessage): InitWalletRequest { + const message = { walletPassword: new Uint8Array(0), cipherSeedMnemonic: [], aezeedPassphrase: new Uint8Array(0), recoveryWindow: 0, statelessInit: false, extendedMasterKey: "", extendedMasterKeyBirthdayTimestamp: 0n, macaroonRootKey: new Uint8Array(0) }; + globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this }); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: InitWalletRequest): InitWalletRequest { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* bytes wallet_password */ 1: + message.walletPassword = reader.bytes(); + break; + case /* repeated string cipher_seed_mnemonic */ 2: + message.cipherSeedMnemonic.push(reader.string()); + break; + case /* bytes aezeed_passphrase */ 3: + message.aezeedPassphrase = reader.bytes(); + break; + case /* int32 recovery_window */ 4: + message.recoveryWindow = reader.int32(); + break; + case /* lnrpc.ChanBackupSnapshot channel_backups */ 5: + message.channelBackups = ChanBackupSnapshot.internalBinaryRead(reader, reader.uint32(), options, message.channelBackups); + break; + case /* bool stateless_init */ 6: + message.statelessInit = reader.bool(); + break; + case /* string extended_master_key */ 7: + message.extendedMasterKey = reader.string(); + break; + case /* uint64 extended_master_key_birthday_timestamp */ 8: + message.extendedMasterKeyBirthdayTimestamp = reader.uint64().toBigInt(); + break; + case /* lnrpc.WatchOnly watch_only */ 9: + message.watchOnly = WatchOnly.internalBinaryRead(reader, reader.uint32(), options, message.watchOnly); + break; + case /* bytes macaroon_root_key */ 10: + message.macaroonRootKey = reader.bytes(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: InitWalletRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* bytes wallet_password = 1; */ + if (message.walletPassword.length) + writer.tag(1, WireType.LengthDelimited).bytes(message.walletPassword); + /* repeated string cipher_seed_mnemonic = 2; */ + for (let i = 0; i < message.cipherSeedMnemonic.length; i++) + writer.tag(2, WireType.LengthDelimited).string(message.cipherSeedMnemonic[i]); + /* bytes aezeed_passphrase = 3; */ + if (message.aezeedPassphrase.length) + writer.tag(3, WireType.LengthDelimited).bytes(message.aezeedPassphrase); + /* int32 recovery_window = 4; */ + if (message.recoveryWindow !== 0) + writer.tag(4, WireType.Varint).int32(message.recoveryWindow); + /* lnrpc.ChanBackupSnapshot channel_backups = 5; */ + if (message.channelBackups) + ChanBackupSnapshot.internalBinaryWrite(message.channelBackups, writer.tag(5, WireType.LengthDelimited).fork(), options).join(); + /* bool stateless_init = 6; */ + if (message.statelessInit !== false) + writer.tag(6, WireType.Varint).bool(message.statelessInit); + /* string extended_master_key = 7; */ + if (message.extendedMasterKey !== "") + writer.tag(7, WireType.LengthDelimited).string(message.extendedMasterKey); + /* uint64 extended_master_key_birthday_timestamp = 8; */ + if (message.extendedMasterKeyBirthdayTimestamp !== 0n) + writer.tag(8, WireType.Varint).uint64(message.extendedMasterKeyBirthdayTimestamp); + /* lnrpc.WatchOnly watch_only = 9; */ + if (message.watchOnly) + WatchOnly.internalBinaryWrite(message.watchOnly, writer.tag(9, WireType.LengthDelimited).fork(), options).join(); + /* bytes macaroon_root_key = 10; */ + if (message.macaroonRootKey.length) + writer.tag(10, WireType.LengthDelimited).bytes(message.macaroonRootKey); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message lnrpc.InitWalletRequest + */ +export const InitWalletRequest = new InitWalletRequest$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class InitWalletResponse$Type extends MessageType { + constructor() { + super("lnrpc.InitWalletResponse", [ + { no: 1, name: "admin_macaroon", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } + ]); + } + create(value?: PartialMessage): InitWalletResponse { + const message = { adminMacaroon: new Uint8Array(0) }; + globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this }); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: InitWalletResponse): InitWalletResponse { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* bytes admin_macaroon */ 1: + message.adminMacaroon = reader.bytes(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: InitWalletResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* bytes admin_macaroon = 1; */ + if (message.adminMacaroon.length) + writer.tag(1, WireType.LengthDelimited).bytes(message.adminMacaroon); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message lnrpc.InitWalletResponse + */ +export const InitWalletResponse = new InitWalletResponse$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class WatchOnly$Type extends MessageType { + constructor() { + super("lnrpc.WatchOnly", [ + { no: 1, name: "master_key_birthday_timestamp", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "master_key_fingerprint", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, + { no: 3, name: "accounts", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => WatchOnlyAccount } + ]); + } + create(value?: PartialMessage): WatchOnly { + const message = { masterKeyBirthdayTimestamp: 0n, masterKeyFingerprint: new Uint8Array(0), accounts: [] }; + globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this }); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: WatchOnly): WatchOnly { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* uint64 master_key_birthday_timestamp */ 1: + message.masterKeyBirthdayTimestamp = reader.uint64().toBigInt(); + break; + case /* bytes master_key_fingerprint */ 2: + message.masterKeyFingerprint = reader.bytes(); + break; + case /* repeated lnrpc.WatchOnlyAccount accounts */ 3: + message.accounts.push(WatchOnlyAccount.internalBinaryRead(reader, reader.uint32(), options)); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: WatchOnly, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* uint64 master_key_birthday_timestamp = 1; */ + if (message.masterKeyBirthdayTimestamp !== 0n) + writer.tag(1, WireType.Varint).uint64(message.masterKeyBirthdayTimestamp); + /* bytes master_key_fingerprint = 2; */ + if (message.masterKeyFingerprint.length) + writer.tag(2, WireType.LengthDelimited).bytes(message.masterKeyFingerprint); + /* repeated lnrpc.WatchOnlyAccount accounts = 3; */ + for (let i = 0; i < message.accounts.length; i++) + WatchOnlyAccount.internalBinaryWrite(message.accounts[i], writer.tag(3, WireType.LengthDelimited).fork(), options).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message lnrpc.WatchOnly + */ +export const WatchOnly = new WatchOnly$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class WatchOnlyAccount$Type extends MessageType { + constructor() { + super("lnrpc.WatchOnlyAccount", [ + { no: 1, name: "purpose", kind: "scalar", T: 13 /*ScalarType.UINT32*/ }, + { no: 2, name: "coin_type", kind: "scalar", T: 13 /*ScalarType.UINT32*/ }, + { no: 3, name: "account", kind: "scalar", T: 13 /*ScalarType.UINT32*/ }, + { no: 4, name: "xpub", kind: "scalar", T: 9 /*ScalarType.STRING*/ } + ]); + } + create(value?: PartialMessage): WatchOnlyAccount { + const message = { purpose: 0, coinType: 0, account: 0, xpub: "" }; + globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this }); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: WatchOnlyAccount): WatchOnlyAccount { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* uint32 purpose */ 1: + message.purpose = reader.uint32(); + break; + case /* uint32 coin_type */ 2: + message.coinType = reader.uint32(); + break; + case /* uint32 account */ 3: + message.account = reader.uint32(); + break; + case /* string xpub */ 4: + message.xpub = reader.string(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: WatchOnlyAccount, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* uint32 purpose = 1; */ + if (message.purpose !== 0) + writer.tag(1, WireType.Varint).uint32(message.purpose); + /* uint32 coin_type = 2; */ + if (message.coinType !== 0) + writer.tag(2, WireType.Varint).uint32(message.coinType); + /* uint32 account = 3; */ + if (message.account !== 0) + writer.tag(3, WireType.Varint).uint32(message.account); + /* string xpub = 4; */ + if (message.xpub !== "") + writer.tag(4, WireType.LengthDelimited).string(message.xpub); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message lnrpc.WatchOnlyAccount + */ +export const WatchOnlyAccount = new WatchOnlyAccount$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class UnlockWalletRequest$Type extends MessageType { + constructor() { + super("lnrpc.UnlockWalletRequest", [ + { no: 1, name: "wallet_password", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, + { no: 2, name: "recovery_window", kind: "scalar", T: 5 /*ScalarType.INT32*/ }, + { no: 3, name: "channel_backups", kind: "message", T: () => ChanBackupSnapshot }, + { no: 4, name: "stateless_init", kind: "scalar", T: 8 /*ScalarType.BOOL*/ } + ]); + } + create(value?: PartialMessage): UnlockWalletRequest { + const message = { walletPassword: new Uint8Array(0), recoveryWindow: 0, statelessInit: false }; + globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this }); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: UnlockWalletRequest): UnlockWalletRequest { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* bytes wallet_password */ 1: + message.walletPassword = reader.bytes(); + break; + case /* int32 recovery_window */ 2: + message.recoveryWindow = reader.int32(); + break; + case /* lnrpc.ChanBackupSnapshot channel_backups */ 3: + message.channelBackups = ChanBackupSnapshot.internalBinaryRead(reader, reader.uint32(), options, message.channelBackups); + break; + case /* bool stateless_init */ 4: + message.statelessInit = reader.bool(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: UnlockWalletRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* bytes wallet_password = 1; */ + if (message.walletPassword.length) + writer.tag(1, WireType.LengthDelimited).bytes(message.walletPassword); + /* int32 recovery_window = 2; */ + if (message.recoveryWindow !== 0) + writer.tag(2, WireType.Varint).int32(message.recoveryWindow); + /* lnrpc.ChanBackupSnapshot channel_backups = 3; */ + if (message.channelBackups) + ChanBackupSnapshot.internalBinaryWrite(message.channelBackups, writer.tag(3, WireType.LengthDelimited).fork(), options).join(); + /* bool stateless_init = 4; */ + if (message.statelessInit !== false) + writer.tag(4, WireType.Varint).bool(message.statelessInit); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message lnrpc.UnlockWalletRequest + */ +export const UnlockWalletRequest = new UnlockWalletRequest$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class UnlockWalletResponse$Type extends MessageType { + constructor() { + super("lnrpc.UnlockWalletResponse", []); + } + create(value?: PartialMessage): UnlockWalletResponse { + const message = {}; + globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this }); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: UnlockWalletResponse): UnlockWalletResponse { + return target ?? this.create(); + } + internalBinaryWrite(message: UnlockWalletResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message lnrpc.UnlockWalletResponse + */ +export const UnlockWalletResponse = new UnlockWalletResponse$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class ChangePasswordRequest$Type extends MessageType { + constructor() { + super("lnrpc.ChangePasswordRequest", [ + { no: 1, name: "current_password", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, + { no: 2, name: "new_password", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, + { no: 3, name: "stateless_init", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }, + { no: 4, name: "new_macaroon_root_key", kind: "scalar", T: 8 /*ScalarType.BOOL*/ } + ]); + } + create(value?: PartialMessage): ChangePasswordRequest { + const message = { currentPassword: new Uint8Array(0), newPassword: new Uint8Array(0), statelessInit: false, newMacaroonRootKey: false }; + globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this }); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ChangePasswordRequest): ChangePasswordRequest { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* bytes current_password */ 1: + message.currentPassword = reader.bytes(); + break; + case /* bytes new_password */ 2: + message.newPassword = reader.bytes(); + break; + case /* bool stateless_init */ 3: + message.statelessInit = reader.bool(); + break; + case /* bool new_macaroon_root_key */ 4: + message.newMacaroonRootKey = reader.bool(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: ChangePasswordRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* bytes current_password = 1; */ + if (message.currentPassword.length) + writer.tag(1, WireType.LengthDelimited).bytes(message.currentPassword); + /* bytes new_password = 2; */ + if (message.newPassword.length) + writer.tag(2, WireType.LengthDelimited).bytes(message.newPassword); + /* bool stateless_init = 3; */ + if (message.statelessInit !== false) + writer.tag(3, WireType.Varint).bool(message.statelessInit); + /* bool new_macaroon_root_key = 4; */ + if (message.newMacaroonRootKey !== false) + writer.tag(4, WireType.Varint).bool(message.newMacaroonRootKey); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message lnrpc.ChangePasswordRequest + */ +export const ChangePasswordRequest = new ChangePasswordRequest$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class ChangePasswordResponse$Type extends MessageType { + constructor() { + super("lnrpc.ChangePasswordResponse", [ + { no: 1, name: "admin_macaroon", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } + ]); + } + create(value?: PartialMessage): ChangePasswordResponse { + const message = { adminMacaroon: new Uint8Array(0) }; + globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this }); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ChangePasswordResponse): ChangePasswordResponse { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* bytes admin_macaroon */ 1: + message.adminMacaroon = reader.bytes(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: ChangePasswordResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* bytes admin_macaroon = 1; */ + if (message.adminMacaroon.length) + writer.tag(1, WireType.LengthDelimited).bytes(message.adminMacaroon); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message lnrpc.ChangePasswordResponse + */ +export const ChangePasswordResponse = new ChangePasswordResponse$Type(); +/** + * @generated ServiceType for protobuf service lnrpc.WalletUnlocker + */ +export const WalletUnlocker = new ServiceType("lnrpc.WalletUnlocker", [ + { name: "GenSeed", options: {}, I: GenSeedRequest, O: GenSeedResponse }, + { name: "InitWallet", options: {}, I: InitWalletRequest, O: InitWalletResponse }, + { name: "UnlockWallet", options: {}, I: UnlockWalletRequest, O: UnlockWalletResponse }, + { name: "ChangePassword", options: {}, I: ChangePasswordRequest, O: ChangePasswordResponse } +]); diff --git a/proto/others/walletunlocker.proto b/proto/others/walletunlocker.proto new file mode 100644 index 00000000..7bcead54 --- /dev/null +++ b/proto/others/walletunlocker.proto @@ -0,0 +1,338 @@ +syntax = "proto3"; + +package lnrpc; + +import "lightning.proto"; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc"; + +/* + * Comments in this file will be directly parsed into the API + * Documentation as descriptions of the associated method, message, or field. + * These descriptions should go right above the definition of the object, and + * can be in either block or // comment format. + * + * An RPC method can be matched to an lncli command by placing a line in the + * beginning of the description in exactly the following format: + * lncli: `methodname` + * + * Failure to specify the exact name of the command will cause documentation + * generation to fail. + * + * More information on how exactly the gRPC documentation is generated from + * this proto file can be found here: + * https://github.com/lightninglabs/lightning-api + */ + +// WalletUnlocker is a service that is used to set up a wallet password for +// lnd at first startup, and unlock a previously set up wallet. +service WalletUnlocker { + /* + GenSeed is the first method that should be used to instantiate a new lnd + instance. This method allows a caller to generate a new aezeed cipher seed + given an optional passphrase. If provided, the passphrase will be necessary + to decrypt the cipherseed to expose the internal wallet seed. + + Once the cipherseed is obtained and verified by the user, the InitWallet + method should be used to commit the newly generated seed, and create the + wallet. + */ + rpc GenSeed (GenSeedRequest) returns (GenSeedResponse); + + /* + InitWallet is used when lnd is starting up for the first time to fully + initialize the daemon and its internal wallet. At the very least a wallet + password must be provided. This will be used to encrypt sensitive material + on disk. + + In the case of a recovery scenario, the user can also specify their aezeed + mnemonic and passphrase. If set, then the daemon will use this prior state + to initialize its internal wallet. + + Alternatively, this can be used along with the GenSeed RPC to obtain a + seed, then present it to the user. Once it has been verified by the user, + the seed can be fed into this RPC in order to commit the new wallet. + */ + rpc InitWallet (InitWalletRequest) returns (InitWalletResponse); + + /* lncli: `unlock` + UnlockWallet is used at startup of lnd to provide a password to unlock + the wallet database. + */ + rpc UnlockWallet (UnlockWalletRequest) returns (UnlockWalletResponse); + + /* lncli: `changepassword` + ChangePassword changes the password of the encrypted wallet. This will + automatically unlock the wallet database if successful. + */ + rpc ChangePassword (ChangePasswordRequest) returns (ChangePasswordResponse); +} + +message GenSeedRequest { + /* + aezeed_passphrase is an optional user provided passphrase that will be used + to encrypt the generated aezeed cipher seed. When using REST, this field + must be encoded as base64. + */ + bytes aezeed_passphrase = 1; + + /* + seed_entropy is an optional 16-bytes generated via CSPRNG. If not + specified, then a fresh set of randomness will be used to create the seed. + When using REST, this field must be encoded as base64. + */ + bytes seed_entropy = 2; +} +message GenSeedResponse { + /* + cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed + cipher seed obtained by the user. This field is optional, as if not + provided, then the daemon will generate a new cipher seed for the user. + Otherwise, then the daemon will attempt to recover the wallet state linked + to this cipher seed. + */ + repeated string cipher_seed_mnemonic = 1; + + /* + enciphered_seed are the raw aezeed cipher seed bytes. This is the raw + cipher text before run through our mnemonic encoding scheme. + */ + bytes enciphered_seed = 2; +} + +message InitWalletRequest { + /* + wallet_password is the passphrase that should be used to encrypt the + wallet. This MUST be at least 8 chars in length. After creation, this + password is required to unlock the daemon. When using REST, this field + must be encoded as base64. + */ + bytes wallet_password = 1; + + /* + cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed + cipher seed obtained by the user. This may have been generated by the + GenSeed method, or be an existing seed. + */ + repeated string cipher_seed_mnemonic = 2; + + /* + aezeed_passphrase is an optional user provided passphrase that will be used + to encrypt the generated aezeed cipher seed. When using REST, this field + must be encoded as base64. + */ + bytes aezeed_passphrase = 3; + + /* + recovery_window is an optional argument specifying the address lookahead + when restoring a wallet seed. The recovery window applies to each + individual branch of the BIP44 derivation paths. Supplying a recovery + window of zero indicates that no addresses should be recovered, such after + the first initialization of the wallet. + */ + int32 recovery_window = 4; + + /* + channel_backups is an optional argument that allows clients to recover the + settled funds within a set of channels. This should be populated if the + user was unable to close out all channels and sweep funds before partial or + total data loss occurred. If specified, then after on-chain recovery of + funds, lnd begin to carry out the data loss recovery protocol in order to + recover the funds in each channel from a remote force closed transaction. + */ + ChanBackupSnapshot channel_backups = 5; + + /* + stateless_init is an optional argument instructing the daemon NOT to create + any *.macaroon files in its filesystem. If this parameter is set, then the + admin macaroon returned in the response MUST be stored by the caller of the + RPC as otherwise all access to the daemon will be lost! + */ + bool stateless_init = 6; + + /* + extended_master_key is an alternative to specifying cipher_seed_mnemonic and + aezeed_passphrase. Instead of deriving the master root key from the entropy + of an aezeed cipher seed, the given extended master root key is used + directly as the wallet's master key. This allows users to import/use a + master key from another wallet. When doing so, lnd still uses its default + SegWit only (BIP49/84) derivation paths and funds from custom/non-default + derivation paths will not automatically appear in the on-chain wallet. Using + an 'xprv' instead of an aezeed also has the disadvantage that the wallet's + birthday is not known as that is an information that's only encoded in the + aezeed, not the xprv. Therefore a birthday needs to be specified in + extended_master_key_birthday_timestamp or a "safe" default value will be + used. + */ + string extended_master_key = 7; + + /* + extended_master_key_birthday_timestamp is the optional unix timestamp in + seconds to use as the wallet's birthday when using an extended master key + to restore the wallet. lnd will only start scanning for funds in blocks that + are after the birthday which can speed up the process significantly. If the + birthday is not known, this should be left at its default value of 0 in + which case lnd will start scanning from the first SegWit block (481824 on + mainnet). + */ + uint64 extended_master_key_birthday_timestamp = 8; + + /* + watch_only is the third option of initializing a wallet: by importing + account xpubs only and therefore creating a watch-only wallet that does not + contain any private keys. That means the wallet won't be able to sign for + any of the keys and _needs_ to be run with a remote signer that has the + corresponding private keys and can serve signing RPC requests. + */ + WatchOnly watch_only = 9; + + /* + macaroon_root_key is an optional 32 byte macaroon root key that can be + provided when initializing the wallet rather than letting lnd generate one + on its own. + */ + bytes macaroon_root_key = 10; +} +message InitWalletResponse { + /* + The binary serialized admin macaroon that can be used to access the daemon + after creating the wallet. If the stateless_init parameter was set to true, + this is the ONLY copy of the macaroon and MUST be stored safely by the + caller. Otherwise a copy of this macaroon is also persisted on disk by the + daemon, together with other macaroon files. + */ + bytes admin_macaroon = 1; +} + +message WatchOnly { + /* + The unix timestamp in seconds of when the master key was created. lnd will + only start scanning for funds in blocks that are after the birthday which + can speed up the process significantly. If the birthday is not known, this + should be left at its default value of 0 in which case lnd will start + scanning from the first SegWit block (481824 on mainnet). + */ + uint64 master_key_birthday_timestamp = 1; + + /* + The fingerprint of the root key (also known as the key with derivation path + m/) from which the account public keys were derived from. This may be + required by some hardware wallets for proper identification and signing. The + bytes must be in big-endian order. + */ + bytes master_key_fingerprint = 2; + + /* + The list of accounts to import. There _must_ be an account for all of lnd's + main key scopes: BIP49/BIP84 (m/49'/0'/0', m/84'/0'/0', note that the + coin type is always 0, even for testnet/regtest) and lnd's internal key + scope (m/1017'/'/'), where account is the key family as + defined in `keychain/derivation.go` (currently indices 0 to 9). + */ + repeated WatchOnlyAccount accounts = 3; +} + +message WatchOnlyAccount { + /* + Purpose is the first number in the derivation path, must be either 49, 84 + or 1017. + */ + uint32 purpose = 1; + + /* + Coin type is the second number in the derivation path, this is _always_ 0 + for purposes 49 and 84. It only needs to be set to 1 for purpose 1017 on + testnet or regtest. + */ + uint32 coin_type = 2; + + /* + Account is the third number in the derivation path. For purposes 49 and 84 + at least the default account (index 0) needs to be created but optional + additional accounts are allowed. For purpose 1017 there needs to be exactly + one account for each of the key families defined in `keychain/derivation.go` + (currently indices 0 to 9) + */ + uint32 account = 3; + + /* + The extended public key at depth 3 for the given account. + */ + string xpub = 4; +} + +message UnlockWalletRequest { + /* + wallet_password should be the current valid passphrase for the daemon. This + will be required to decrypt on-disk material that the daemon requires to + function properly. When using REST, this field must be encoded as base64. + */ + bytes wallet_password = 1; + + /* + recovery_window is an optional argument specifying the address lookahead + when restoring a wallet seed. The recovery window applies to each + individual branch of the BIP44 derivation paths. Supplying a recovery + window of zero indicates that no addresses should be recovered, such after + the first initialization of the wallet. + */ + int32 recovery_window = 2; + + /* + channel_backups is an optional argument that allows clients to recover the + settled funds within a set of channels. This should be populated if the + user was unable to close out all channels and sweep funds before partial or + total data loss occurred. If specified, then after on-chain recovery of + funds, lnd begin to carry out the data loss recovery protocol in order to + recover the funds in each channel from a remote force closed transaction. + */ + ChanBackupSnapshot channel_backups = 3; + + /* + stateless_init is an optional argument instructing the daemon NOT to create + any *.macaroon files in its file system. + */ + bool stateless_init = 4; +} +message UnlockWalletResponse { +} + +message ChangePasswordRequest { + /* + current_password should be the current valid passphrase used to unlock the + daemon. When using REST, this field must be encoded as base64. + */ + bytes current_password = 1; + + /* + new_password should be the new passphrase that will be needed to unlock the + daemon. When using REST, this field must be encoded as base64. + */ + bytes new_password = 2; + + /* + stateless_init is an optional argument instructing the daemon NOT to create + any *.macaroon files in its filesystem. If this parameter is set, then the + admin macaroon returned in the response MUST be stored by the caller of the + RPC as otherwise all access to the daemon will be lost! + */ + bool stateless_init = 3; + + /* + new_macaroon_root_key is an optional argument instructing the daemon to + rotate the macaroon root key when set to true. This will invalidate all + previously generated macaroons. + */ + bool new_macaroon_root_key = 4; +} +message ChangePasswordResponse { + /* + The binary serialized admin macaroon that can be used to access the daemon + after rotating the macaroon root key. If both the stateless_init and + new_macaroon_root_key parameter were set to true, this is the ONLY copy of + the macaroon that was created from the new root key and MUST be stored + safely by the caller. Otherwise a copy of this macaroon is also persisted on + disk by the daemon, together with other macaroon files. + */ + bytes admin_macaroon = 1; +} \ No newline at end of file diff --git a/src/services/lnd/initWalletReq.ts b/src/services/lnd/initWalletReq.ts new file mode 100644 index 00000000..5287b0bf --- /dev/null +++ b/src/services/lnd/initWalletReq.ts @@ -0,0 +1,15 @@ +import { InitWalletRequest } from "../../../proto/lnd/walletunlocker"; + + +export const InitWalletReq = (secret: Buffer, cipherSeedMnemonic: string[]): InitWalletRequest => ({ + aezeedPassphrase: Buffer.alloc(0), + walletPassword: secret, + cipherSeedMnemonic, + extendedMasterKey: "", + extendedMasterKeyBirthdayTimestamp: 0n, + macaroonRootKey: Buffer.alloc(0), + recoveryWindow: 0, + statelessInit: false, + channelBackups: undefined, + watchOnly: undefined, +}) \ No newline at end of file diff --git a/src/services/main/init.ts b/src/services/main/init.ts index 6aa06b1a..a799d9cb 100644 --- a/src/services/main/init.ts +++ b/src/services/main/init.ts @@ -1,5 +1,6 @@ import { PubLogger, getLogger } from "../helpers/logger.js" import { LiquidityProvider } from "../lnd/liquidityProvider.js" +import { Unlocker } from "./unlocker.js" import Storage from "../storage/index.js" import { TypeOrmMigrationRunner } from "../storage/migrations/runner.js" import Main from "./index.js" @@ -17,6 +18,8 @@ export const initMainHandler = async (log: PubLogger, mainSettings: MainSettings if (manualMigration) { return } + const unlocker = new Unlocker(mainSettings, storageManager) + await unlocker.Unlock() const mainHandler = new Main(mainSettings, storageManager) await mainHandler.lnd.Warmup() diff --git a/src/services/main/settings.ts b/src/services/main/settings.ts index b856c953..0b1f3617 100644 --- a/src/services/main/settings.ts +++ b/src/services/main/settings.ts @@ -13,6 +13,7 @@ export type MainSettings = { watchDogSettings: WatchdogSettings, liquiditySettings: LiquiditySettings, jwtSecret: string + walletSecretPath: string incomingTxFee: number outgoingTxFee: number incomingAppInvoiceFee: number @@ -27,11 +28,13 @@ export type MainSettings = { skipSanityCheck: boolean disableExternalPayments: boolean } + export type BitcoinCoreSettings = { port: number user: string pass: string } + export type TestSettings = MainSettings & { lndSettings: { otherNode: NodeSettings, thirdNode: NodeSettings, fourthNode: NodeSettings }, bitcoinCoreSettings: BitcoinCoreSettings } export const LoadMainSettingsFromEnv = (): MainSettings => { const storageSettings = LoadStorageSettingsFromEnv() @@ -41,6 +44,7 @@ export const LoadMainSettingsFromEnv = (): MainSettings => { storageSettings: storageSettings, liquiditySettings: LoadLiquiditySettingsFromEnv(), jwtSecret: loadJwtSecret(storageSettings.dataDir), + walletSecretPath: process.env.WALLET_SECRET_PATH || getDataPath(storageSettings.dataDir, ".wallet_secret"), incomingTxFee: EnvCanBeInteger("INCOMING_CHAIN_FEE_ROOT_BPS", 0) / 10000, outgoingTxFee: EnvCanBeInteger("OUTGOING_CHAIN_FEE_ROOT_BPS", 60) / 10000, incomingAppInvoiceFee: EnvCanBeInteger("INCOMING_INVOICE_FEE_ROOT_BPS", 0) / 10000, @@ -101,7 +105,7 @@ export const loadJwtSecret = (dataDir: string): string => { return secret } log("JWT_SECRET not set in env, checking .jwt_secret file") - const secretPath = dataDir !== "" ? `${dataDir}/.jwt_secret` : ".jwt_secret" + const secretPath = getDataPath(dataDir, ".jwt_secret") try { const fileContent = fs.readFileSync(secretPath, "utf-8") return fileContent.trim() @@ -111,4 +115,8 @@ export const loadJwtSecret = (dataDir: string): string => { fs.writeFileSync(secretPath, secret) return secret } +} + +export const getDataPath = (dataDir: string, dataPath: string) => { + return dataDir !== "" ? `${dataDir}/${dataPath}` : dataPath } \ No newline at end of file diff --git a/src/services/main/unlocker.ts b/src/services/main/unlocker.ts new file mode 100644 index 00000000..2626842a --- /dev/null +++ b/src/services/main/unlocker.ts @@ -0,0 +1,210 @@ +import fs from 'fs' +import crypto from 'crypto' +import { GrpcTransport } from "@protobuf-ts/grpc-transport"; +import { credentials, Metadata } from '@grpc/grpc-js' +import { getLogger } from '../helpers/logger.js'; +import { WalletUnlockerClient } from '../../../proto/lnd/walletunlocker.client.js'; +import { MainSettings } from '../main/settings.js'; +import { InitWalletReq } from '../lnd/initWalletReq.js'; +import Storage from '../storage/index.js' +import { LightningClient } from '../../../proto/lnd/lightning.client.js'; +const DeadLineMetadata = (deadline = 10 * 1000) => ({ deadline: Date.now() + deadline }) +export class Unlocker { + settings: MainSettings + storage: Storage + abortController = new AbortController() + log = getLogger({ component: "unlocker" }) + constructor(settings: MainSettings, storage: Storage) { + this.settings = settings + this.storage = storage + } + + Stop = () => { + this.abortController.abort() + } + + Unlock = async () => { + const macroonPath = this.settings.lndSettings.mainNode.lndMacaroonPath + const certPath = this.settings.lndSettings.mainNode.lndCertPath + let macaroon = "" + let lndCert: Buffer + try { + lndCert = fs.readFileSync(certPath) + } catch (err: any) { + throw new Error("failed to access lnd cert, make sure to set LND_CERT_PATH in .env, that the path is correct, and that lnd is running") + } + try { + macaroon = fs.readFileSync(macroonPath).toString('hex'); + } catch (err: any) { + if (err.code !== 'ENOENT') { + throw err + } + } + const { ln, pub } = macaroon === "" ? await this.InitFlow(lndCert) : await this.UnlockFlow(lndCert, macaroon) + this.subscribeToBackups(ln, pub) + } + + UnlockFlow = async (lndCert: Buffer, macaroon: string) => { + const ln = this.GetLightningClient(lndCert, macaroon) + const info = await this.GetLndInfo(ln) + if (info.ok) { + this.log("the wallet is already unlocked with pub:", info.pub) + return { ln, pub: info.pub } + } + if (info.failure !== 'locked') { + throw new Error("failed to get lnd info for reason: " + info.failure) + } + this.log("wallet is locked, unlocking...") + const secret = this.GetWalletSecret(false) + if (!secret) { + throw new Error("wallet secret not found to unlock wallet") + } + const unlocker = this.GetUnlockerClient(lndCert) + await unlocker.unlockWallet({ walletPassword: Buffer.from(secret, 'hex'), recoveryWindow: 0, statelessInit: false, channelBackups: undefined }, DeadLineMetadata()) + const infoAfter = await this.GetLndInfo(ln) + if (!infoAfter.ok) { + throw new Error("failed to init lnd wallet " + infoAfter.failure) + } + this.log("unlocked wallet with pub:", infoAfter.pub) + return { ln, pub: infoAfter.pub } + } + + InitFlow = async (lndCert: Buffer) => { + this.log("macaroon not found, creating wallet...") + const unlocker = this.GetUnlockerClient(lndCert) + const entropy = crypto.randomBytes(16) + const seedRes = await unlocker.genSeed({ + aezeedPassphrase: Buffer.alloc(0), + seedEntropy: entropy + }, DeadLineMetadata()) + console.log(seedRes.response.cipherSeedMnemonic) + console.log(seedRes.response.encipheredSeed) + this.log("seed created, encrypting and saving...") + const { encryptedData, secret } = this.EncryptWalletSeed(seedRes.response.cipherSeedMnemonic) + const req = InitWalletReq(secret, seedRes.response.cipherSeedMnemonic) + const initRes = await unlocker.initWallet(req, DeadLineMetadata(60 * 1000)) + const adminMacaroon = Buffer.from(initRes.response.adminMacaroon).toString('hex') + const ln = this.GetLightningClient(lndCert, adminMacaroon) + const info = await this.GetLndInfo(ln) + if (!info.ok) { + throw new Error("failed to init lnd wallet " + info.failure) + } + await this.storage.liquidityStorage.SaveNodeSeed(info.pub, JSON.stringify(encryptedData)) + this.log("created wallet with pub:", info.pub) + return { ln, pub: info.pub } + } + + GetLndInfo = async (ln: LightningClient): Promise<{ ok: false, failure: 'locked' | 'unknown' } | { ok: true, pub: string }> => { + while (true) { + try { + const info = await ln.getInfo({}, DeadLineMetadata()) + return { ok: true, pub: info.response.identityPubkey } + } catch (err: any) { + if (err.message === '2 UNKNOWN: wallet locked, unlock it to enable full RPC access') { + this.log("wallet is locked") + return { ok: false, failure: 'locked' } + } else if (err.message === '2 UNKNOWN: the RPC server is in the process of starting up, but not yet ready to accept calls') { + this.log("lnd is not ready yet, waiting...") + await new Promise((res) => setTimeout(res, 1000)) + } else { + this.log("failed to get lnd info", err.message) + return { ok: false, failure: 'unknown' } + } + } + } + } + + EncryptWalletSeed = (seed: string[]) => { + return this.encrypt(seed.join('+')) + } + + DecryptWalletSeed = (data: { iv: string, encrypted: string }) => { + return this.decrypt(data).split('+') + } + EncryptBackup = (backup: Buffer) => { + return this.encrypt(backup.toString('hex')) + } + + DecryptBackup = (data: { iv: string, encrypted: string }) => { + return Buffer.from(this.decrypt(data), 'hex') + } + + encrypt = (text: string) => { + const sec = this.GetWalletSecret(true) + const secret = Buffer.from(sec, 'hex') + const iv = crypto.randomBytes(16) + const cipher = crypto.createCipheriv('aes-256-cbc', secret, iv) + const rawData = Buffer.from(text, 'utf-8') + const cyData = cipher.update(rawData) + const encrypted = Buffer.concat([cyData, cipher.final()]) + const encryptedData = { iv: iv.toString('hex'), encrypted: encrypted.toString('hex') } + return { encryptedData, secret } + } + + decrypt = (data: { iv: string, encrypted: string }) => { + const sec = this.GetWalletSecret(false) + if (!sec) { + throw new Error("wallet secret not found to decrypt seed") + } + const secret = Buffer.from(sec, 'hex') + const iv = Buffer.from(data.iv, 'hex') + const encrypted = Buffer.from(data.encrypted, 'hex') + const decipher = crypto.createDecipheriv('aes-256-cbc', secret, iv) + const decrypted = decipher.update(encrypted) + const raw = Buffer.concat([decrypted, decipher.final()]) + return raw.toString('utf-8') + } + + GetWalletSecret = (create: boolean) => { + const path = this.settings.walletSecretPath + let secret = "" + try { + secret = fs.readFileSync(path, 'utf-8') + } catch { + this.log("the wallet secret file was not found") + } + if (secret === "" && create) { + secret = crypto.randomBytes(32).toString('hex') + fs.writeFileSync(path, secret) + } + return secret + } + + subscribeToBackups = async (ln: LightningClient, pub: string) => { + this.log("subscribing to channel backups for: ", pub) + const stream = ln.subscribeChannelBackups({}, { abort: this.abortController.signal }) + stream.responses.onMessage((msg) => { + if (msg.multiChanBackup) { + this.log("received backup, saving") + const { encryptedData } = this.EncryptBackup(Buffer.from(msg.multiChanBackup.multiChanBackup)) + this.storage.liquidityStorage.SaveNodeBackup(pub, JSON.stringify(encryptedData)) + } + }) + } + + GetUnlockerClient = (cert: Buffer) => { + const host = this.settings.lndSettings.mainNode.lndAddr + const channelCredentials = credentials.createSsl(cert) + const transport = new GrpcTransport({ host, channelCredentials }) + const client = new WalletUnlockerClient(transport) + return client + } + GetLightningClient = (cert: Buffer, macaroon: string) => { + const host = this.settings.lndSettings.mainNode.lndAddr + const sslCreds = credentials.createSsl(cert) + const macaroonCreds = credentials.createFromMetadataGenerator( + function (args: any, callback: any) { + let metadata = new Metadata(); + metadata.add('macaroon', macaroon); + callback(null, metadata); + }, + ); + const channelCredentials = credentials.combineChannelCredentials( + sslCreds, + macaroonCreds, + ); + const transport = new GrpcTransport({ host, channelCredentials }) + const client = new LightningClient(transport) + return client + } +} \ No newline at end of file diff --git a/src/services/storage/db.ts b/src/services/storage/db.ts index 657e4724..74ca9917 100644 --- a/src/services/storage/db.ts +++ b/src/services/storage/db.ts @@ -9,7 +9,6 @@ import { EnvMustBeNonEmptyString } from "../helpers/envParser.js" import { UserTransactionPayment } from "./entity/UserTransactionPayment.js" import { UserBasicAuth } from "./entity/UserBasicAuth.js" import { UserEphemeralKey } from "./entity/UserEphemeralKey.js" -import { Product } from "./entity/Product.js" import { UserToUserPayment } from "./entity/UserToUserPayment.js" import { Application } from "./entity/Application.js" import { ApplicationUser } from "./entity/ApplicationUser.js" @@ -18,6 +17,8 @@ import { ChannelBalanceEvent } from "./entity/ChannelsBalanceEvent.js" import { getLogger } from "../helpers/logger.js" import { ChannelRouting } from "./entity/ChannelRouting.js" import { LspOrder } from "./entity/LspOrder.js" +import { Product } from "./entity/Product.js" +import { LndNodeInfo } from "./entity/LndNodeInfo.js" export type DbSettings = { @@ -57,7 +58,7 @@ export default async (settings: DbSettings, migrations: Function[]): Promise<{ s database: settings.databaseFile, // logging: true, entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment, - UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, LspOrder], + UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, LspOrder, LndNodeInfo], //synchronize: true, migrations }).initialize() diff --git a/src/services/storage/entity/LndNodeInfo.ts b/src/services/storage/entity/LndNodeInfo.ts new file mode 100644 index 00000000..f6c40603 --- /dev/null +++ b/src/services/storage/entity/LndNodeInfo.ts @@ -0,0 +1,24 @@ +import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from "typeorm" +import { User } from "./User.js" + +@Entity() +export class LndNodeInfo { + + @PrimaryGeneratedColumn() + serial_id: number + + @Column() + pubkey: string + + @Column({ nullable: true }) + seed?: string + + @Column({ nullable: true }) + backup?: string + + @CreateDateColumn() + created_at: Date + + @UpdateDateColumn() + updated_at: Date +} diff --git a/src/services/storage/liquidityStorage.ts b/src/services/storage/liquidityStorage.ts index 2ee2e92a..65c7f9f2 100644 --- a/src/services/storage/liquidityStorage.ts +++ b/src/services/storage/liquidityStorage.ts @@ -1,6 +1,7 @@ import { DataSource, EntityManager, MoreThan } from "typeorm" import { LspOrder } from "./entity/LspOrder.js"; import TransactionsQueue, { TX } from "./transactionsQueue.js"; +import { LndNodeInfo } from "./entity/LndNodeInfo.js"; export class LiquidityStorage { DB: DataSource | EntityManager txQueue: TransactionsQueue @@ -17,4 +18,23 @@ export class LiquidityStorage { const entry = this.DB.getRepository(LspOrder).create(order) return this.txQueue.PushToQueue({ exec: async db => db.getRepository(LspOrder).save(entry), dbTx: false }) } + + async SaveNodeSeed(pubkey: string, seed: string) { + const existing = await this.DB.getRepository(LndNodeInfo).findOne({ where: { pubkey } }) + if (existing) { + throw new Error("A seed already exists for this pub key") + } + const entry = this.DB.getRepository(LndNodeInfo).create({ pubkey, seed }) + return this.txQueue.PushToQueue({ exec: async db => db.getRepository(LndNodeInfo).save(entry), dbTx: false }) + } + + async SaveNodeBackup(pubkey: string, backup: string) { + const existing = await this.DB.getRepository(LndNodeInfo).findOne({ where: { pubkey } }) + if (existing) { + await this.DB.getRepository(LndNodeInfo).update(existing.serial_id, { backup }) + return + } + const entry = this.DB.getRepository(LndNodeInfo).create({ pubkey, backup }) + await this.txQueue.PushToQueue({ exec: async db => db.getRepository(LndNodeInfo).save(entry), dbTx: false }) + } } \ No newline at end of file diff --git a/src/services/storage/migrations/1720187506189-lnd_node_info.ts b/src/services/storage/migrations/1720187506189-lnd_node_info.ts new file mode 100644 index 00000000..39285f69 --- /dev/null +++ b/src/services/storage/migrations/1720187506189-lnd_node_info.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class LndNodeInfo1720187506189 implements MigrationInterface { + name = 'LndNodeInfo1720187506189' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "lnd_node_info" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "pubkey" varchar NOT NULL, "seed" varchar, "backup" varchar, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')))`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE "lnd_node_info"`); + } + +} diff --git a/src/services/storage/migrations/runner.ts b/src/services/storage/migrations/runner.ts index 60755344..a014aa99 100644 --- a/src/services/storage/migrations/runner.ts +++ b/src/services/storage/migrations/runner.ts @@ -6,7 +6,8 @@ import { LndMetrics1703170330183 } from './1703170330183-lnd_metrics.js' import { ChannelRouting1709316653538 } from './1709316653538-channel_routing.js' import { LspOrder1718387847693 } from './1718387847693-lsp_order.js' import { LiquidityProvider1719335699480 } from './1719335699480-liquidity_provider.js' -const allMigrations = [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480] +import { LndNodeInfo1720187506189 } from './1720187506189-lnd_node_info.js' +const allMigrations = [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189] const allMetricsMigrations = [LndMetrics1703170330183, ChannelRouting1709316653538] export const TypeOrmMigrationRunner = async (log: PubLogger, storageManager: Storage, settings: DbSettings, arg: string | undefined): Promise => { if (arg === 'fake_initial_migration') {