diff --git a/src/services/lnd/initWalletReq.ts b/src/services/lnd/initWalletReq.ts index 5287b0bf..e76617d1 100644 --- a/src/services/lnd/initWalletReq.ts +++ b/src/services/lnd/initWalletReq.ts @@ -1,9 +1,9 @@ import { InitWalletRequest } from "../../../proto/lnd/walletunlocker"; -export const InitWalletReq = (secret: Buffer, cipherSeedMnemonic: string[]): InitWalletRequest => ({ +export const InitWalletReq = (walletPw: Buffer, cipherSeedMnemonic: string[]): InitWalletRequest => ({ aezeedPassphrase: Buffer.alloc(0), - walletPassword: secret, + walletPassword: walletPw, cipherSeedMnemonic, extendedMasterKey: "", extendedMasterKeyBirthdayTimestamp: 0n, diff --git a/src/services/main/settings.ts b/src/services/main/settings.ts index 0b1f3617..c6455c45 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 + walletPasswordPath: string walletSecretPath: string incomingTxFee: number outgoingTxFee: number @@ -45,6 +46,7 @@ export const LoadMainSettingsFromEnv = (): MainSettings => { liquiditySettings: LoadLiquiditySettingsFromEnv(), jwtSecret: loadJwtSecret(storageSettings.dataDir), walletSecretPath: process.env.WALLET_SECRET_PATH || getDataPath(storageSettings.dataDir, ".wallet_secret"), + walletPasswordPath: process.env.WALLET_PASSWORD_PATH || getDataPath(storageSettings.dataDir, ".wallet_password"), 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, diff --git a/src/services/main/unlocker.ts b/src/services/main/unlocker.ts index 2626842a..6d7ffdfb 100644 --- a/src/services/main/unlocker.ts +++ b/src/services/main/unlocker.ts @@ -55,12 +55,9 @@ export class Unlocker { 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 walletPassword = this.GetWalletPassword() + await unlocker.unlockWallet({ walletPassword, 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) @@ -80,8 +77,9 @@ export class Unlocker { 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 { encryptedData } = this.EncryptWalletSeed(seedRes.response.cipherSeedMnemonic) + const walletPw = this.GetWalletPassword() + const req = InitWalletReq(walletPw, 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) @@ -115,7 +113,7 @@ export class Unlocker { } EncryptWalletSeed = (seed: string[]) => { - return this.encrypt(seed.join('+')) + return this.encrypt(seed.join('+'), true) } DecryptWalletSeed = (data: { iv: string, encrypted: string }) => { @@ -129,8 +127,11 @@ export class Unlocker { return Buffer.from(this.decrypt(data), 'hex') } - encrypt = (text: string) => { - const sec = this.GetWalletSecret(true) + encrypt = (text: string, must = false) => { + const sec = this.GetWalletSecret(must) + if (!sec) { + throw new Error("wallet secret not found to encrypt") + } const secret = Buffer.from(sec, 'hex') const iv = crypto.randomBytes(16) const cipher = crypto.createCipheriv('aes-256-cbc', secret, iv) @@ -138,13 +139,13 @@ export class Unlocker { 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 } + return { encryptedData } } decrypt = (data: { iv: string, encrypted: string }) => { const sec = this.GetWalletSecret(false) if (!sec) { - throw new Error("wallet secret not found to decrypt seed") + throw new Error("wallet secret not found to decrypt") } const secret = Buffer.from(sec, 'hex') const iv = Buffer.from(data.iv, 'hex') @@ -164,20 +165,43 @@ export class Unlocker { this.log("the wallet secret file was not found") } if (secret === "" && create) { + this.log("creating wallet secret file") secret = crypto.randomBytes(32).toString('hex') fs.writeFileSync(path, secret) } return secret } + GetWalletPassword = () => { + const path = this.settings.walletPasswordPath + let password = Buffer.alloc(0) + try { + password = fs.readFileSync(path) + } catch { + } + if (password.length === 0) { + this.log("no wallet password configured, using wallet secret") + const secret = this.GetWalletSecret(false) + if (secret === "") { + throw new Error("no usable password found") + } + password = Buffer.from(secret, 'hex') + } + return password + } + 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) => { + stream.responses.onMessage(async (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)) + try { + const { encryptedData } = this.EncryptBackup(Buffer.from(msg.multiChanBackup.multiChanBackup)) + await this.storage.liquidityStorage.SaveNodeBackup(pub, JSON.stringify(encryptedData)) + } catch (err: any) { + this.log("failed to save backup", err.message) + } } }) }