diff --git a/backend/backend.go b/backend/backend.go index 13c2231bbf..a21d642b22 100644 --- a/backend/backend.go +++ b/backend/backend.go @@ -196,6 +196,15 @@ type Environment interface { // OnAuthSettingChanged is called when the authentication (screen lock) setting is changed. // This is also called when the app launches with the current setting. OnAuthSettingChanged(enabled bool) + // CanEncryptLightningMnemonic reports whether Lightning mnemonics should be stored encrypted on + // this platform. + CanEncryptLightningMnemonic() bool + // StoreLightningEncryptionKey persists a backend-generated Lightning seed encryption key. + StoreLightningEncryptionKey(accountCode string, encryptionKey string) error + // LoadLightningEncryptionKey retrieves the stored Lightning seed encryption key. + LoadLightningEncryptionKey(accountCode string) (string, error) + // DeleteLightningEncryptionKey removes the persisted Lightning seed encryption key. + DeleteLightningEncryptionKey(accountCode string) error // BluetoothConnect tries to connect to the peripheral by the given identifier. // Use `backend.bluetooth.State()` to track failure. BluetoothConnect(identifier string) @@ -349,6 +358,7 @@ func NewBackend(arguments *arguments.Arguments, environment Environment) (*Backe backend.lightning = lightning.NewLightning(backend.config, backend.arguments.CacheDirectoryPath(), + backend.environment, backend.Keystore, backend.httpClient, backend.ratesUpdater, btcCoin) diff --git a/backend/backend_test.go b/backend/backend_test.go index 983af5227e..de1190e40f 100644 --- a/backend/backend_test.go +++ b/backend/backend_test.go @@ -245,6 +245,14 @@ func (e environment) Auth() {} func (e environment) OnAuthSettingChanged(bool) {} +func (e environment) CanEncryptLightningMnemonic() bool { return false } + +func (e environment) StoreLightningEncryptionKey(string, string) error { return nil } + +func (e environment) LoadLightningEncryptionKey(string) (string, error) { return "", nil } + +func (e environment) DeleteLightningEncryptionKey(string) error { return nil } + func (e environment) BluetoothConnect(string) {} type mockTransactionsSource struct { diff --git a/backend/bridgecommon/bridgecommon.go b/backend/bridgecommon/bridgecommon.go index c99dbb9f87..7bfffea77f 100644 --- a/backend/bridgecommon/bridgecommon.go +++ b/backend/bridgecommon/bridgecommon.go @@ -180,13 +180,17 @@ type BackendEnvironment struct { UsingMobileDataFunc func() bool // NativeLocaleFunc is used by the backend to query native app layer for user // preferred UI language. - NativeLocaleFunc func() string - GetSaveFilenameFunc func(string) string - SetDarkThemeFunc func(bool) - DetectDarkThemeFunc func() bool - AuthFunc func() - OnAuthSettingChangedFunc func(bool) - BluetoothConnectFunc func(string) + NativeLocaleFunc func() string + GetSaveFilenameFunc func(string) string + SetDarkThemeFunc func(bool) + DetectDarkThemeFunc func() bool + AuthFunc func() + OnAuthSettingChangedFunc func(bool) + CanEncryptLightningMnemonicFunc func() bool + StoreLightningEncryptionKeyFunc func(string, string) error + LoadLightningEncryptionKeyFunc func(string) (string, error) + DeleteLightningEncryptionKeyFunc func(string) error + BluetoothConnectFunc func(string) } // NotifyUser implements backend.Environment. @@ -265,6 +269,38 @@ func (env *BackendEnvironment) OnAuthSettingChanged(enabled bool) { } } +// CanEncryptLightningMnemonic implements backend.Environment. +func (env *BackendEnvironment) CanEncryptLightningMnemonic() bool { + if env.CanEncryptLightningMnemonicFunc != nil { + return env.CanEncryptLightningMnemonicFunc() + } + return false +} + +// StoreLightningEncryptionKey implements backend.Environment. +func (env *BackendEnvironment) StoreLightningEncryptionKey(accountCode string, encryptionKey string) error { + if env.StoreLightningEncryptionKeyFunc != nil { + return env.StoreLightningEncryptionKeyFunc(accountCode, encryptionKey) + } + return nil +} + +// LoadLightningEncryptionKey implements backend.Environment. +func (env *BackendEnvironment) LoadLightningEncryptionKey(accountCode string) (string, error) { + if env.LoadLightningEncryptionKeyFunc != nil { + return env.LoadLightningEncryptionKeyFunc(accountCode) + } + return "", nil +} + +// DeleteLightningEncryptionKey implements backend.Environment. +func (env *BackendEnvironment) DeleteLightningEncryptionKey(accountCode string) error { + if env.DeleteLightningEncryptionKeyFunc != nil { + return env.DeleteLightningEncryptionKeyFunc(accountCode) + } + return nil +} + // BluetoothConnect implements backend.Environment. func (env *BackendEnvironment) BluetoothConnect(identifier string) { if env.BluetoothConnectFunc != nil { diff --git a/backend/bridgecommon/bridgecommon_test.go b/backend/bridgecommon/bridgecommon_test.go index 296ada0464..1e30c117b6 100644 --- a/backend/bridgecommon/bridgecommon_test.go +++ b/backend/bridgecommon/bridgecommon_test.go @@ -61,6 +61,14 @@ func (e environment) Auth() {} func (e environment) OnAuthSettingChanged(bool) {} +func (e environment) CanEncryptLightningMnemonic() bool { return false } + +func (e environment) StoreLightningEncryptionKey(string, string) error { return nil } + +func (e environment) LoadLightningEncryptionKey(string) (string, error) { return "", nil } + +func (e environment) DeleteLightningEncryptionKey(string) error { return nil } + func (e environment) BluetoothConnect(string) {} // TestServeShutdownServe checks that you can call Serve twice in a row. diff --git a/backend/handlers/handlers_test.go b/backend/handlers/handlers_test.go index fe22e4dd1d..cd936be110 100644 --- a/backend/handlers/handlers_test.go +++ b/backend/handlers/handlers_test.go @@ -38,17 +38,21 @@ type backendEnv struct { Locale string // returned by NativeLocale } -func (e *backendEnv) NotifyUser(string) {} -func (e *backendEnv) SystemOpen(string) error { return nil } -func (e *backendEnv) DeviceInfos() []usb.DeviceInfo { return nil } -func (e *backendEnv) UsingMobileData() bool { return false } -func (e *backendEnv) NativeLocale() string { return e.Locale } -func (e *backendEnv) GetSaveFilename(string) string { return "" } -func (e *backendEnv) SetDarkTheme(bool) {} -func (e *backendEnv) DetectDarkTheme() bool { return false } -func (e *backendEnv) Auth() {} -func (e *backendEnv) OnAuthSettingChanged(bool) {} -func (e *backendEnv) BluetoothConnect(string) {} +func (e *backendEnv) NotifyUser(string) {} +func (e *backendEnv) SystemOpen(string) error { return nil } +func (e *backendEnv) DeviceInfos() []usb.DeviceInfo { return nil } +func (e *backendEnv) UsingMobileData() bool { return false } +func (e *backendEnv) NativeLocale() string { return e.Locale } +func (e *backendEnv) GetSaveFilename(string) string { return "" } +func (e *backendEnv) SetDarkTheme(bool) {} +func (e *backendEnv) DetectDarkTheme() bool { return false } +func (e *backendEnv) Auth() {} +func (e *backendEnv) OnAuthSettingChanged(bool) {} +func (e *backendEnv) CanEncryptLightningMnemonic() bool { return false } +func (e *backendEnv) StoreLightningEncryptionKey(string, string) error { return nil } +func (e *backendEnv) LoadLightningEncryptionKey(string) (string, error) { return "", nil } +func (e *backendEnv) DeleteLightningEncryptionKey(string) error { return nil } +func (e *backendEnv) BluetoothConnect(string) {} func TestGetNativeLocale(t *testing.T) { const ptLocale = "pt" diff --git a/backend/lightning/lightning.go b/backend/lightning/lightning.go index 873a41f981..9e7eec81f4 100644 --- a/backend/lightning/lightning.go +++ b/backend/lightning/lightning.go @@ -3,7 +3,12 @@ package lightning import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" "encoding/hex" + "io" "net/http" "os" "path" @@ -29,12 +34,21 @@ const ( breezApiKeyUrl = "https://bitboxapp.shiftcrypto.dev/lightning/breez-api-key" ) +// Keep this local to avoid importing backend.Environment and creating a package cycle. +type environment interface { + CanEncryptLightningMnemonic() bool + StoreLightningEncryptionKey(accountCode string, encryptionKey string) error + LoadLightningEncryptionKey(accountCode string) (string, error) + DeleteLightningEncryptionKey(accountCode string) error +} + // Lightning manages the Breez SDK lightning node. type Lightning struct { observable.Implementation backendConfig *config.Config cacheDirectoryPath string + environment environment getKeystore func() keystore.Keystore synced bool @@ -48,6 +62,7 @@ type Lightning struct { // NewLightning creates a new instance of the Lightning struct. func NewLightning(config *config.Config, cacheDirectoryPath string, + environment environment, getKeystore func() keystore.Keystore, httpClient *http.Client, ratesUpdater *rates.RateUpdater, @@ -55,6 +70,7 @@ func NewLightning(config *config.Config, return &Lightning{ backendConfig: config, cacheDirectoryPath: cacheDirectoryPath, + environment: environment, getKeystore: getKeystore, log: logging.Get().WithGroup("lightning"), synced: false, @@ -64,7 +80,8 @@ func NewLightning(config *config.Config, } } -// Activate first creates a mnemonic from the keystore entropy then connects to instance. +// Activate first creates a mnemonic from the keystore entropy, persists it, and connects to the +// instance. func (lightning *Lightning) Activate() error { if lightning.Account() != nil { return errp.New("Lightning accounts already configured") @@ -91,13 +108,25 @@ func (lightning *Lightning) Activate() error { return errp.New("Error generating mnemonic") } + accountCode := types.Code(strings.Join([]string{"v0-", hex.EncodeToString(fingerprint), "-ln-0"}, "")) + sealedMnemonic, err := lightning.sealMnemonic(string(accountCode), entropyMnemonic) + if err != nil { + lightning.log.WithError(err).Warn("Error configuring Lightning secure storage") + return errp.New("Could not configure Lightning secure storage on this device") + } + lightningAccount := config.LightningAccountConfig{ - Mnemonic: entropyMnemonic, + Mnemonic: sealedMnemonic, RootFingerprint: fingerprint, - Code: types.Code(strings.Join([]string{"v0-", hex.EncodeToString(fingerprint), "-ln-0"}, "")), + Code: accountCode, Number: 0, } if err = lightning.SetAccount(&lightningAccount); err != nil { + if lightning.environment.CanEncryptLightningMnemonic() { + if deleteErr := lightning.environment.DeleteLightningEncryptionKey(string(accountCode)); deleteErr != nil { + lightning.log.WithError(deleteErr).Warn("Error deleting lightning encryption key after activation failure") + } + } return err } @@ -149,6 +178,12 @@ func (lightning *Lightning) Deactivate() error { return err } + if lightning.environment.CanEncryptLightningMnemonic() { + if err := lightning.environment.DeleteLightningEncryptionKey(string(account.Code)); err != nil { + lightning.log.WithError(err).Warn("Error deleting lightning encryption key") + } + } + return nil } @@ -172,7 +207,6 @@ func (lightning *Lightning) Balance() (*accounts.Balance, error) { // before returning the balance EnsureSynced: &ensureSynced, }) - if err != nil { return nil, err } @@ -201,9 +235,15 @@ func (lightning *Lightning) connect(_ bool) error { return err } + mnemonic, err := lightning.unsealMnemonic(account) + if err != nil { + lightning.log.WithError(err).Warn("Error unlocking Lightning mnemonic") + return errp.New("Error unlocking Lightning mnemonic from the device") + } + // Construct the seed using mnemonic words or entropy bytes var seed breez_sdk_spark.Seed = breez_sdk_spark.SeedMnemonic{ - Mnemonic: account.Mnemonic, + Mnemonic: mnemonic, Passphrase: nil, } @@ -219,7 +259,9 @@ func (lightning *Lightning) connect(_ bool) error { config.PrivateEnabledDefault = true // Set the maximum fee to the fastest network recommended fee at the time of claim // with a leeway of 1 sats/vbyte - networkRecommendedInterface := breez_sdk_spark.MaxFee(breez_sdk_spark.MaxFeeNetworkRecommended{LeewaySatPerVbyte: 1}) + networkRecommendedInterface := breez_sdk_spark.MaxFee( + breez_sdk_spark.MaxFeeNetworkRecommended{LeewaySatPerVbyte: 1}, + ) config.MaxDepositClaimFee = &networkRecommendedInterface connectRequest := breez_sdk_spark.ConnectRequest{ @@ -252,6 +294,96 @@ func (lightning *Lightning) connect(_ bool) error { return nil } +func (lightning *Lightning) sealMnemonic(accountCode string, mnemonic string) (string, error) { + if !lightning.environment.CanEncryptLightningMnemonic() { + return mnemonic, nil + } + + encryptionKey := make([]byte, 32) + if _, err := io.ReadFull(rand.Reader, encryptionKey); err != nil { + return "", err + } + + sealedMnemonic, err := encryptMnemonic(mnemonic, encryptionKey) + if err != nil { + return "", err + } + + if err := lightning.environment.StoreLightningEncryptionKey( + accountCode, + base64.StdEncoding.EncodeToString(encryptionKey), + ); err != nil { + return "", err + } + + return sealedMnemonic, nil +} + +func (lightning *Lightning) unsealMnemonic(account *config.LightningAccountConfig) (string, error) { + if !lightning.environment.CanEncryptLightningMnemonic() { + return account.Mnemonic, nil + } + + encryptionKeyBase64, err := lightning.environment.LoadLightningEncryptionKey(string(account.Code)) + if err != nil { + return "", err + } + + encryptionKey, err := base64.StdEncoding.DecodeString(encryptionKeyBase64) + if err != nil { + return "", err + } + + mnemonic, err := decryptMnemonic(account.Mnemonic, encryptionKey) + if err != nil { + return "", err + } + + return mnemonic, nil +} + +func encryptMnemonic(mnemonic string, encryptionKey []byte) (string, error) { + block, err := aes.NewCipher(encryptionKey) + if err != nil { + return "", err + } + gcm, err := cipher.NewGCM(block) + if err != nil { + return "", err + } + nonce := make([]byte, gcm.NonceSize()) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + return "", err + } + ciphertext := gcm.Seal(nil, nonce, []byte(mnemonic), nil) + return base64.StdEncoding.EncodeToString(append(nonce, ciphertext...)), nil +} + +func decryptMnemonic(sealedMnemonic string, encryptionKey []byte) (string, error) { + rawCiphertext, err := base64.StdEncoding.DecodeString(sealedMnemonic) + if err != nil { + return "", err + } + block, err := aes.NewCipher(encryptionKey) + if err != nil { + return "", err + } + gcm, err := cipher.NewGCM(block) + if err != nil { + return "", err + } + if len(rawCiphertext) < gcm.NonceSize() { + return "", errp.New("ciphertext too short") + } + nonce := rawCiphertext[:gcm.NonceSize()] + ciphertext := rawCiphertext[gcm.NonceSize():] + plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) + if err != nil { + return "", err + } + return string(plaintext), nil +} + func (lightning *Lightning) getBreezApiKey() (*string, error) { _, breezApiKey, err := util.HTTPGet(lightning.httpClient, breezApiKeyUrl, "", int64(4096)) if err != nil { diff --git a/backend/lightning/lightning_test.go b/backend/lightning/lightning_test.go index b58081579c..afeab411ae 100644 --- a/backend/lightning/lightning_test.go +++ b/backend/lightning/lightning_test.go @@ -12,7 +12,47 @@ import ( "github.com/stretchr/testify/require" ) -func newTestLightning(t *testing.T) *Lightning { +type testEnvironment struct { + canEncrypt bool + keys map[string]string + storeErr error + loadErr error + deleteErr error + loadCalls int +} + +func (e *testEnvironment) CanEncryptLightningMnemonic() bool { + return e.canEncrypt +} + +func (e *testEnvironment) StoreLightningEncryptionKey(accountCode string, encryptionKey string) error { + if e.storeErr != nil { + return e.storeErr + } + if e.keys == nil { + e.keys = map[string]string{} + } + e.keys[accountCode] = encryptionKey + return nil +} + +func (e *testEnvironment) LoadLightningEncryptionKey(accountCode string) (string, error) { + e.loadCalls++ + if e.loadErr != nil { + return "", e.loadErr + } + return e.keys[accountCode], nil +} + +func (e *testEnvironment) DeleteLightningEncryptionKey(accountCode string) error { + if e.deleteErr != nil { + return e.deleteErr + } + delete(e.keys, accountCode) + return nil +} + +func newTestLightning(t *testing.T, environment environment) *Lightning { t.Helper() appConfigFilename := test.TstTempFile("appConfig") @@ -22,9 +62,14 @@ func newTestLightning(t *testing.T) *Lightning { cfg, err := config.NewConfig(appConfigFilename, accountsConfigFilename, lightningConfigFilename) require.NoError(t, err) + if environment == nil { + environment = &testEnvironment{} + } + return NewLightning( cfg, test.TstTempDir("lightning-cache"), + environment, func() keystore.Keystore { return nil }, &http.Client{}, nil, @@ -33,7 +78,7 @@ func newTestLightning(t *testing.T) *Lightning { } func TestAccount(t *testing.T) { - lightning := newTestLightning(t) + lightning := newTestLightning(t, nil) require.Nil(t, lightning.Account()) require.NoError(t, lightning.backendConfig.ModifyLightningConfig(func(cfg *config.LightningConfig) error { @@ -50,7 +95,7 @@ func TestAccount(t *testing.T) { } func TestSetAccount(t *testing.T) { - lightning := newTestLightning(t) + lightning := newTestLightning(t, nil) account := &config.LightningAccountConfig{ Mnemonic: "test mnemonic", @@ -73,3 +118,28 @@ func TestSetAccount(t *testing.T) { require.Nil(t, lightning.Account()) require.Empty(t, lightning.backendConfig.LightningConfig().Accounts) } + +func TestSealAndUnsealMnemonicEncrypted(t *testing.T) { + env := &testEnvironment{canEncrypt: true} + lightning := newTestLightning(t, env) + + sealedMnemonic, err := lightning.sealMnemonic("v0-deadbeef-ln-0", "test mnemonic") + require.NoError(t, err) + require.NotEqual(t, "test mnemonic", sealedMnemonic) + + mnemonic, err := lightning.unsealMnemonic(&config.LightningAccountConfig{ + Code: "v0-deadbeef-ln-0", + Mnemonic: sealedMnemonic, + }) + require.NoError(t, err) + require.Equal(t, "test mnemonic", mnemonic) + require.Equal(t, 1, env.loadCalls) + + mnemonic, err = lightning.unsealMnemonic(&config.LightningAccountConfig{ + Code: "v0-deadbeef-ln-0", + Mnemonic: sealedMnemonic, + }) + require.NoError(t, err) + require.Equal(t, "test mnemonic", mnemonic) + require.Equal(t, 2, env.loadCalls) +} diff --git a/backend/mobileserver/mobileserver.go b/backend/mobileserver/mobileserver.go index 4d70086166..756cff0524 100644 --- a/backend/mobileserver/mobileserver.go +++ b/backend/mobileserver/mobileserver.go @@ -74,6 +74,10 @@ type GoEnvironmentInterface interface { DetectDarkTheme() bool Auth() OnAuthSettingChanged(bool) + CanEncryptLightningMnemonic() bool + StoreLightningEncryptionKey(string, string) error + LoadLightningEncryptionKey(string) (string, error) + DeleteLightningEncryptionKey(string) error BluetoothConnect(string) } @@ -185,15 +189,19 @@ func Serve(dataDir string, testnet bool, environment GoEnvironmentInterface, goA } return []usb.DeviceInfo{deviceInfo{i}} }, - SystemOpenFunc: environment.SystemOpen, - UsingMobileDataFunc: environment.UsingMobileData, - NativeLocaleFunc: environment.NativeLocale, - GetSaveFilenameFunc: environment.GetSaveFilename, - SetDarkThemeFunc: environment.SetDarkTheme, - DetectDarkThemeFunc: environment.DetectDarkTheme, - AuthFunc: environment.Auth, - OnAuthSettingChangedFunc: environment.OnAuthSettingChanged, - BluetoothConnectFunc: environment.BluetoothConnect, + SystemOpenFunc: environment.SystemOpen, + UsingMobileDataFunc: environment.UsingMobileData, + NativeLocaleFunc: environment.NativeLocale, + GetSaveFilenameFunc: environment.GetSaveFilename, + SetDarkThemeFunc: environment.SetDarkTheme, + DetectDarkThemeFunc: environment.DetectDarkTheme, + AuthFunc: environment.Auth, + OnAuthSettingChangedFunc: environment.OnAuthSettingChanged, + CanEncryptLightningMnemonicFunc: environment.CanEncryptLightningMnemonic, + StoreLightningEncryptionKeyFunc: environment.StoreLightningEncryptionKey, + LoadLightningEncryptionKeyFunc: environment.LoadLightningEncryptionKey, + DeleteLightningEncryptionKeyFunc: environment.DeleteLightningEncryptionKey, + BluetoothConnectFunc: environment.BluetoothConnect, }, ) } diff --git a/cmd/servewallet/main.go b/cmd/servewallet/main.go index c4e9cbaf5d..3c8b860cef 100644 --- a/cmd/servewallet/main.go +++ b/cmd/servewallet/main.go @@ -92,6 +92,26 @@ func (webdevEnvironment) Auth() { func (webdevEnvironment) OnAuthSettingChanged(enabled bool) { } +// CanEncryptLightningMnemonic implements backend.Environment. +func (webdevEnvironment) CanEncryptLightningMnemonic() bool { + return false +} + +// StoreLightningEncryptionKey implements backend.Environment. +func (webdevEnvironment) StoreLightningEncryptionKey(accountCode string, encryptionKey string) error { + return nil +} + +// LoadLightningEncryptionKey implements backend.Environment. +func (webdevEnvironment) LoadLightningEncryptionKey(accountCode string) (string, error) { + return "", nil +} + +// DeleteLightningEncryptionKey implements backend.Environment. +func (webdevEnvironment) DeleteLightningEncryptionKey(accountCode string) error { + return nil +} + // BluetoothConnect implements backend.Environment. func (webdevEnvironment) BluetoothConnect(identifier string) { } diff --git a/frontends/android/BitBoxApp/app/src/main/AndroidManifest.xml b/frontends/android/BitBoxApp/app/src/main/AndroidManifest.xml index dfc1c8ad71..b11278c035 100644 --- a/frontends/android/BitBoxApp/app/src/main/AndroidManifest.xml +++ b/frontends/android/BitBoxApp/app/src/main/AndroidManifest.xml @@ -30,6 +30,8 @@ + + + + diff --git a/frontends/android/BitBoxApp/app/src/main/res/xml/data_extraction_rules.xml b/frontends/android/BitBoxApp/app/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 0000000000..ec489d4e2c --- /dev/null +++ b/frontends/android/BitBoxApp/app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/frontends/ios/BitBoxApp/BitBoxApp/BitBoxAppApp.swift b/frontends/ios/BitBoxApp/BitBoxApp/BitBoxAppApp.swift index 965aea4f91..10650008a1 100644 --- a/frontends/ios/BitBoxApp/BitBoxApp/BitBoxAppApp.swift +++ b/frontends/ios/BitBoxApp/BitBoxApp/BitBoxAppApp.swift @@ -91,6 +91,20 @@ class GoEnvironment: NSObject, MobileserverGoEnvironmentInterfaceProtocol, UIDoc // TODO: hide app window contents in app switcher, maybe always not just when auth is on. } + func canEncryptLightningMnemonic() -> Bool { + false + } + + func storeLightningEncryptionKey(_ accountCode: String?, p1 encryptionKey: String?) throws { + } + + func loadLightningEncryptionKey(_ accountCode: String?, error: NSErrorPointer) -> String { + "" + } + + func deleteLightningEncryptionKey(_ accountCode: String?) throws { + } + func bluetoothConnect(_ identifier: String?) { guard let identifier = identifier else { return diff --git a/frontends/qt/server/server.go b/frontends/qt/server/server.go index 1913e69d6e..a0cd771267 100644 --- a/frontends/qt/server/server.go +++ b/frontends/qt/server/server.go @@ -196,8 +196,12 @@ func serve( log.Info("Qt auth") authResult(mobileserver.AuthResultOk) }, - OnAuthSettingChangedFunc: func(bool) {}, - BluetoothConnectFunc: func(string) {}, + OnAuthSettingChangedFunc: func(bool) {}, + CanEncryptLightningMnemonicFunc: func() bool { return false }, + StoreLightningEncryptionKeyFunc: func(string, string) error { return nil }, + LoadLightningEncryptionKeyFunc: func(string) (string, error) { return "", nil }, + DeleteLightningEncryptionKeyFunc: func(string) error { return nil }, + BluetoothConnectFunc: func(string) {}, }, ) }