diff --git a/lib/background/breez_service_initializer.dart b/lib/background/breez_service_initializer.dart index 6b792167f..7e752fd15 100644 --- a/lib/background/breez_service_initializer.dart +++ b/lib/background/breez_service_initializer.dart @@ -1,7 +1,7 @@ // ignore_for_file: avoid_print import 'package:bip39/bip39.dart' as bip39; import 'package:breez_sdk/breez_bridge.dart'; -import 'package:c_breez/bloc/account/credential_manager.dart'; +import 'package:c_breez/bloc/account/credentials_manager.dart'; import 'package:c_breez/config.dart'; import 'package:c_breez/services/injector.dart'; @@ -12,16 +12,11 @@ Future initializeBreezServices() async { print("Is Breez Services initialized: $isBreezInitialized"); if (!isBreezInitialized) { final credentialsManager = CredentialsManager(keyChain: injector.keychain); - final credentials = await credentialsManager.restoreCredentials(); - final seed = bip39.mnemonicToSeed(credentials.mnemonic); + final mnemonic = await credentialsManager.restoreMnemonic(); + final seed = bip39.mnemonicToSeed(mnemonic); print("Retrieved credentials"); - await breezLib.initServices( - config: (await Config.instance()).sdkConfig, - seed: seed, - creds: credentials.glCreds, - ); + await breezLib.connect(config: (await Config.instance()).sdkConfig, seed: seed); print("Initialized Services"); - await breezLib.startNode(); print("Node has started"); } await breezLib.syncNode(); diff --git a/lib/bloc/account/account_bloc.dart b/lib/bloc/account/account_bloc.dart index af5d6314e..ad9a95fc9 100644 --- a/lib/bloc/account/account_bloc.dart +++ b/lib/bloc/account/account_bloc.dart @@ -5,7 +5,7 @@ import 'package:bip39/bip39.dart' as bip39; import 'package:breez_sdk/breez_bridge.dart'; import 'package:breez_sdk/bridge_generated.dart' as sdk; import 'package:c_breez/bloc/account/account_state.dart'; -import 'package:c_breez/bloc/account/credential_manager.dart'; +import 'package:c_breez/bloc/account/credentials_manager.dart'; import 'package:c_breez/bloc/account/payment_error.dart'; import 'package:c_breez/bloc/account/payment_filters.dart'; import 'package:c_breez/bloc/account/payment_result.dart'; @@ -51,7 +51,7 @@ class AccountBloc extends Cubit with HydratedMixin { _paymentFiltersStreamController.add(state.paymentFilters); - if (!state.initial) _startRegisteredNode(); + if (!state.initial) connect(); _listenPaymentResultEvents(); } @@ -68,65 +68,17 @@ class AccountBloc extends Cubit with HydratedMixin { ); } - Future _startRegisteredNode() async { - _log.v("starting registered node"); - final credentials = await _credentialsManager.restoreCredentials(); - final seed = bip39.mnemonicToSeed(credentials.mnemonic); - await _breezLib.initServices( - config: (await Config.instance()).sdkConfig, - seed: seed, - creds: credentials.glCreds, - ); - await _startSdkForever(); - } - - // startNewNode register a new node and start it - Future startNewNode({ - sdk.Network network = sdk.Network.Bitcoin, - required String mnemonic, - }) async { - _log.v("starting new node"); - final appConf = await Config.instance(); - final seed = bip39.mnemonicToSeed(mnemonic); - final sdk.GreenlightCredentials creds = await _breezLib.registerNode( - config: (await Config.instance()).sdkConfig, - network: network, - seed: seed, - registerCredentials: - sdk.GreenlightCredentials(deviceKey: appConf.glKey!, deviceCert: appConf.glCert!)); - _log.i("node registered successfully"); - await _credentialsManager.storeCredentials( - glCreds: creds, - mnemonic: mnemonic, - ); - emit(state.copyWith(initial: false)); - await _startSdkForever(); - _log.i("new node started"); - } - - // recoverNode recovers a node from seed - Future recoverNode({ - sdk.Network network = sdk.Network.Bitcoin, - required String mnemonic, + Future connect({ + String? mnemonic, }) async { - _log.v("recovering node"); - final seed = bip39.mnemonicToSeed(mnemonic); - final sdk.GreenlightCredentials creds = await _breezLib.recoverNode( - config: (await Config.instance()).sdkConfig, - network: network, - seed: seed, - ); - _log.i("node recovered successfully"); - await _credentialsManager.storeCredentials( - glCreds: creds, - mnemonic: mnemonic, - ); - emit(state.copyWith( - initial: false, - verificationStatus: VerificationStatus.VERIFIED, - )); + if (mnemonic != null) { + await _credentialsManager.storeMnemonic(mnemonic: mnemonic); + emit(state.copyWith( + initial: false, + verificationStatus: VerificationStatus.VERIFIED, + )); + } await _startSdkForever(); - _log.i("recovered node started"); } Future _startSdkForever() async { @@ -155,7 +107,9 @@ class AccountBloc extends Cubit with HydratedMixin { _log.v("starting sdk once"); try { emit(state.copyWith(connectionStatus: ConnectionStatus.CONNECTING)); - await _breezLib.startNode(); + final mnemonic = await _credentialsManager.restoreMnemonic(); + final seed = bip39.mnemonicToSeed(mnemonic); + await _breezLib.connect(config: (await Config.instance()).sdkConfig, seed: seed); emit(state.copyWith(connectionStatus: ConnectionStatus.CONNECTED)); } catch (e) { emit(state.copyWith(connectionStatus: ConnectionStatus.DISCONNECTED)); diff --git a/lib/bloc/account/credential_manager.dart b/lib/bloc/account/credential_manager.dart deleted file mode 100644 index 64a78321c..000000000 --- a/lib/bloc/account/credential_manager.dart +++ /dev/null @@ -1,122 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; -import 'dart:typed_data'; - -import 'package:breez_sdk/bridge_generated.dart'; -import 'package:c_breez/services/keychain.dart'; -import 'package:fimber/fimber.dart'; -import 'package:hex/hex.dart'; -import 'package:path_provider/path_provider.dart'; - -class CredentialsManager { - final _log = FimberLog("CredentialsManager"); - static const String accountCredsKey = "account_creds_key"; - static const String accountCredsCert = "account_creds_cert"; - static const String accountMnemonic = "account_mnemonic"; - - final KeyChain keyChain; - - CredentialsManager({required this.keyChain}); - - Future storeCredentials({ - required GreenlightCredentials glCreds, - required String mnemonic, - }) async { - try { - await _storeGreenlightCredentials(glCreds); - await _storeMnemonic(mnemonic); - _log.i("Stored credentials successfully"); - } catch (err) { - throw Exception(err.toString()); - } - } - - Future restoreCredentials() async { - try { - GreenlightCredentials glCreds = await _restoreGreenlightCredentials(); - String mnemonic = await _restoreMnemonic(); - _log.i("Restored credentials successfully"); - return Credentials(glCreds: glCreds, mnemonic: mnemonic); - } catch (err) { - throw Exception(err.toString()); - } - } - - // Helper methods - Future _storeGreenlightCredentials( - GreenlightCredentials glCreds, - ) async { - await keyChain.write(accountCredsCert, HEX.encode(glCreds.deviceCert)); - await keyChain.write(accountCredsKey, HEX.encode(glCreds.deviceKey)); - } - - Future _storeMnemonic(String mnemonic) async { - await keyChain.write(accountMnemonic, mnemonic); - } - - Future _restoreGreenlightCredentials() async { - String? deviceCertStr = await keyChain.read(accountCredsCert); - String? deviceKeyStr = await keyChain.read(accountCredsKey); - return GreenlightCredentials( - deviceKey: Uint8List.fromList(HEX.decode(deviceKeyStr!)), - deviceCert: Uint8List.fromList(HEX.decode(deviceCertStr!)), - ); - } - - Future _restoreMnemonic() async { - String? mnemonicStr = await keyChain.read(accountMnemonic); - return mnemonicStr!; - } - - Future> exportCredentials() async { - try { - final Directory tempDir = await getTemporaryDirectory(); - var keysDir = tempDir.createTempSync("keys"); - final File credentialsFile = await File('${keysDir.path}/creds').create(recursive: true); - Credentials credentials = await restoreCredentials(); - credentialsFile.writeAsString(jsonEncode(credentials.toGreenlightCredentialsJson())); - final File mnemonicFile = await File('${keysDir.path}/phrase').create(recursive: true); - mnemonicFile.writeAsString(credentials.mnemonic); - return [credentialsFile, mnemonicFile]; - } catch (e) { - throw e.toString(); - } - } -} - -class Credentials { - final GreenlightCredentials glCreds; - final String mnemonic; - - Credentials({required this.glCreds, required this.mnemonic}); - - GreenlightCredentials fromGreenlightCredentialsJson( - Map json, - ) { - return GreenlightCredentials( - deviceKey: Uint8List.fromList(json['deviceKey']), - deviceCert: Uint8List.fromList(json['deviceCert']), - ); - } - - Map toGreenlightCredentialsJson() => { - 'deviceKey': glCreds.deviceKey, - 'deviceCert': glCreds.deviceCert, - }; - - Credentials.fromJson( - Map json, - ) : glCreds = GreenlightCredentials( - deviceKey: Uint8List.fromList(HEX.decode(json['glCreds']['deviceKey'])), - deviceCert: Uint8List.fromList(HEX.decode(json['glCreds']['deviceCert'])), - ), - mnemonic = json['mnemonic']; - - Map toJson() => { - 'glCreds': { - 'deviceKey': HEX.encode(glCreds.deviceKey), - 'deviceCert': HEX.encode(glCreds.deviceCert), - }, - 'mnemonic': mnemonic, - }; -} diff --git a/lib/bloc/account/credentials_manager.dart b/lib/bloc/account/credentials_manager.dart new file mode 100644 index 000000000..90233e309 --- /dev/null +++ b/lib/bloc/account/credentials_manager.dart @@ -0,0 +1,53 @@ +import 'dart:io'; + +import 'package:c_breez/services/keychain.dart'; +import 'package:fimber/fimber.dart'; +import 'package:path_provider/path_provider.dart'; + +class CredentialsManager { + final _log = FimberLog("CredentialsManager"); + static const String accountMnemonic = "account_mnemonic"; + + final KeyChain keyChain; + + CredentialsManager({required this.keyChain}); + + Future storeMnemonic({ + required String mnemonic, + }) async { + try { + await _storeMnemonic(mnemonic); + _log.i("Stored credentials successfully"); + } catch (err) { + throw Exception(err.toString()); + } + } + + Future restoreMnemonic() async { + try { + String? mnemonicStr = await keyChain.read(accountMnemonic); + _log.i("Restored credentials successfully"); + return mnemonicStr!; + } catch (err) { + throw Exception(err.toString()); + } + } + + // Helper methods + Future _storeMnemonic(String mnemonic) async { + await keyChain.write(accountMnemonic, mnemonic); + } + + Future> exportCredentials() async { + try { + final Directory tempDir = await getTemporaryDirectory(); + var keysDir = tempDir.createTempSync("keys"); + final File mnemonicFile = await File('${keysDir.path}/phrase').create(recursive: true); + String mnemonic = await restoreMnemonic(); + mnemonicFile.writeAsString(mnemonic); + return [mnemonicFile]; + } catch (e) { + throw e.toString(); + } + } +} diff --git a/lib/main.dart b/lib/main.dart index 63650809f..55a6ea1a7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,7 +6,7 @@ import 'dart:io'; import 'package:c_breez/background/background_task_handler.dart'; import 'package:c_breez/background/breez_message_handler.dart'; import 'package:c_breez/bloc/account/account_bloc.dart'; -import 'package:c_breez/bloc/account/credential_manager.dart'; +import 'package:c_breez/bloc/account/credentials_manager.dart'; import 'package:c_breez/bloc/backup/backup_bloc.dart'; import 'package:c_breez/bloc/buy_bitcoin/moonpay/moonpay_bloc.dart'; import 'package:c_breez/bloc/connectivity/connectivity_bloc.dart'; diff --git a/lib/routes/home/widgets/app_bar/account_required_actions.dart b/lib/routes/home/widgets/app_bar/account_required_actions.dart index 85f65d412..e31978c51 100644 --- a/lib/routes/home/widgets/app_bar/account_required_actions.dart +++ b/lib/routes/home/widgets/app_bar/account_required_actions.dart @@ -1,6 +1,6 @@ import 'package:c_breez/bloc/account/account_bloc.dart'; import 'package:c_breez/bloc/account/account_state.dart'; -import 'package:c_breez/bloc/account/credential_manager.dart'; +import 'package:c_breez/bloc/account/credentials_manager.dart'; import 'package:c_breez/bloc/backup/backup_bloc.dart'; import 'package:c_breez/bloc/backup/backup_state.dart'; import 'package:c_breez/bloc/ext/block_builder_extensions.dart'; diff --git a/lib/routes/initial_walkthrough/initial_walkthrough.dart b/lib/routes/initial_walkthrough/initial_walkthrough.dart index 86ad2c5b4..2604b5c4e 100644 --- a/lib/routes/initial_walkthrough/initial_walkthrough.dart +++ b/lib/routes/initial_walkthrough/initial_walkthrough.dart @@ -148,11 +148,12 @@ class InitialWalkthroughPageState extends State return AlphaWarningDialog(); }, ); - if (approved) _startNewNode(); + if (approved) connect(); } - void _startNewNode() async { - _log.v("Starting new node"); + void connect({String? mnemonic}) async { + final isRestore = mnemonic != null; + _log.v("${isRestore ? "Restore" : "Starting new"} node"); final texts = context.texts(); final accountBloc = context.read(); final navigator = Navigator.of(context); @@ -161,10 +162,12 @@ class InitialWalkthroughPageState extends State final themeProvider = ThemeProvider.controllerOf(context); try { - String mnemonic = bip39.generateMnemonic(strength: 128); - await accountBloc.startNewNode(mnemonic: mnemonic); + await accountBloc.connect(mnemonic: mnemonic ?? bip39.generateMnemonic(strength: 128)); } catch (error) { - _log.i("Failed to register node", ex: error); + _log.i("Failed to ${isRestore ? "restore" : "registe"} node", ex: error); + if (isRestore) { + _restoreNodeFromMnemonicSeed(initialWords: mnemonic.split(" ")); + } showFlushbar(context, message: extractExceptionMessage(error, texts)); return; } finally { @@ -181,7 +184,7 @@ class InitialWalkthroughPageState extends State _log.v("Restore node from mnemonic seed"); String? mnemonic = await _getMnemonic(initialWords: initialWords); if (mnemonic != null) { - restoreNode(mnemonic); + connect(mnemonic: mnemonic); } } @@ -194,28 +197,4 @@ class InitialWalkthroughPageState extends State arguments: initialWords, ); } - - void restoreNode(String mnemonic) async { - _log.v("Restore node"); - final texts = context.texts(); - final accountBloc = context.read(); - final navigator = Navigator.of(context); - var loaderRoute = createLoaderRoute(context); - navigator.push(loaderRoute); - - final themeProvider = ThemeProvider.controllerOf(context); - try { - await accountBloc.recoverNode(mnemonic: mnemonic); - } catch (error) { - _log.w("Failed to restore node", ex: error); - _restoreNodeFromMnemonicSeed(initialWords: mnemonic.split(" ")); - showFlushbar(context, message: extractExceptionMessage(error, texts)); - return; - } finally { - navigator.removeRoute(loaderRoute); - } - - themeProvider.setTheme('dark'); - navigator.pushReplacementNamed('/'); - } } diff --git a/lib/routes/security/widget/mnemonics/security_mnemonics_management.dart b/lib/routes/security/widget/mnemonics/security_mnemonics_management.dart index ecee9efcf..5f0c01183 100644 --- a/lib/routes/security/widget/mnemonics/security_mnemonics_management.dart +++ b/lib/routes/security/widget/mnemonics/security_mnemonics_management.dart @@ -1,7 +1,7 @@ import 'package:breez_translations/breez_translations_locales.dart'; import 'package:c_breez/bloc/account/account_bloc.dart'; import 'package:c_breez/bloc/account/account_state.dart'; -import 'package:c_breez/bloc/account/credential_manager.dart'; +import 'package:c_breez/bloc/account/credentials_manager.dart'; import 'package:c_breez/routes/initial_walkthrough/mnemonics/mnemonics_page.dart'; import 'package:c_breez/services/injector.dart'; import 'package:c_breez/widgets/route.dart'; diff --git a/test/bloc/account/account_bloc_test.dart b/test/bloc/account/account_bloc_test.dart index e4a087ab2..fab76d30f 100644 --- a/test/bloc/account/account_bloc_test.dart +++ b/test/bloc/account/account_bloc_test.dart @@ -1,6 +1,6 @@ import 'package:c_breez/bloc/account/account_bloc.dart'; import 'package:c_breez/bloc/account/account_state.dart'; -import 'package:c_breez/bloc/account/credential_manager.dart'; +import 'package:c_breez/bloc/account/credentials_manager.dart'; import 'package:c_breez/services/injector.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; @@ -36,12 +36,10 @@ void main() { test('recover node', () async { var injector = InjectorMock(); var breezLib = injector.breezLib; - injector.keychain.write(CredentialsManager.accountCredsKey, "a3e1"); - injector.keychain.write(CredentialsManager.accountCredsCert, "a3e61"); injector.keychain.write(CredentialsManager.accountMnemonic, "a3eed"); AccountBloc accBloc = AccountBloc(breezLib, CredentialsManager(keyChain: injector.keychain)); - await accBloc.recoverNode(mnemonic: testMnemonic); + await accBloc.connect(mnemonic: testMnemonic); var accountState = accBloc.state; expect(accountState.blockheight, greaterThan(1)); expect(accountState.id?.length, equals(66)); diff --git a/test/mock/breez_bridge_mock.dart b/test/mock/breez_bridge_mock.dart index 7059a65a4..f137e4b2f 100644 --- a/test/mock/breez_bridge_mock.dart +++ b/test/mock/breez_bridge_mock.dart @@ -13,11 +13,12 @@ class BreezBridgeMock extends Mock implements BreezBridge { ); @override - Future initServices({ + Future connect({ required Config config, required Uint8List seed, - required GreenlightCredentials creds, - }) async {} + }) async { + await getNodeState(); + } Config config = const Config( breezserver: '', @@ -26,21 +27,16 @@ class BreezBridgeMock extends Mock implements BreezBridge { paymentTimeoutSec: 10, workingDir: '.', maxfeePercent: 0.5, + nodeConfig: NodeConfig_Greenlight(config: GreenlightNodeConfig()), ); @override - Future defaultConfig(EnvironmentType envType) async { - return config; - } - - @override - Future recoverNode({ - required Config config, - required Network network, - required Uint8List seed, + Future defaultConfig({ + required EnvironmentType envType, + required String apiKey, + required NodeConfig nodeConfig, }) async { - await getNodeState(); - return credentials; + return config; } NodeState? nodeState = const NodeState(