From 3f1ce644b08546a5a1c6e138b708865a9e42ecf3 Mon Sep 17 00:00:00 2001 From: Marc Platt Date: Mon, 27 Jan 2025 21:15:45 -0500 Subject: [PATCH 01/12] launcher: restore starter functionality to launch with l2 --- clients/launcher/lib/pages/overview_page.dart | 2 - .../launcher/lib/services/wallet_service.dart | 90 ++++++++----------- clients/launcher/test/test_utils.dart | 2 +- .../sail_ui/lib/classes/rpc_connection.dart | 11 +++ .../lib/providers/binary_provider.dart | 85 ++++++++++++++---- 5 files changed, 118 insertions(+), 72 deletions(-) diff --git a/clients/launcher/lib/pages/overview_page.dart b/clients/launcher/lib/pages/overview_page.dart index 9b27011d..e1fe429d 100644 --- a/clients/launcher/lib/pages/overview_page.dart +++ b/clients/launcher/lib/pages/overview_page.dart @@ -48,7 +48,6 @@ class _OverviewPageState extends State { WidgetsBinding.instance.addPostFrameCallback((_) { _binaryProvider.addListener(_onBinaryProviderUpdate); _processProvider.addListener(_onBinaryProviderUpdate); - _walletService.addListener(_onBinaryProviderUpdate); _blockchainProvider.addListener(_onBinaryProviderUpdate); }); } @@ -57,7 +56,6 @@ class _OverviewPageState extends State { void dispose() { _binaryProvider.removeListener(_onBinaryProviderUpdate); _processProvider.removeListener(_onBinaryProviderUpdate); - _walletService.removeListener(_onBinaryProviderUpdate); _blockchainProvider.removeListener(_onBinaryProviderUpdate); super.dispose(); } diff --git a/clients/launcher/lib/services/wallet_service.dart b/clients/launcher/lib/services/wallet_service.dart index d2daeb56..063002b9 100644 --- a/clients/launcher/lib/services/wallet_service.dart +++ b/clients/launcher/lib/services/wallet_service.dart @@ -6,7 +6,6 @@ import 'package:convert/convert.dart'; import 'package:crypto/crypto.dart'; import 'package:dart_bip32_bip44/dart_bip32_bip44.dart'; import 'package:flutter/foundation.dart'; -import 'package:get_it/get_it.dart'; import 'package:launcher/env.dart'; import 'package:logger/logger.dart'; import 'package:path/path.dart' as path; @@ -18,11 +17,12 @@ import 'package:pointycastle/macs/hmac.dart'; import 'package:pointycastle/digests/sha512.dart'; class WalletService extends ChangeNotifier { - BinaryProvider get binaryProvider => GetIt.I.get(); - + final BinaryProvider binaryProvider; final _logger = Logger(); static const String defaultBip32Path = "m/44'/0'/0'"; + WalletService(this.binaryProvider); + Future hasExistingWallet() async { final walletFile = await _getWalletFile('master_starter.json'); return walletFile.existsSync(); @@ -40,51 +40,25 @@ class WalletService extends ChangeNotifier { final chain = Chain.seed(seedHex); final masterKey = chain.forPath('m') as ExtendedPrivateKey; - final walletData = { - 'mnemonic': mnemonicObj.sentence, - 'seed_hex': seedHex, - 'master_key': masterKey.privateKeyHex(), - 'chain_code': hex.encode(masterKey.chainCode!), - 'bip39_binary': _bytesToBinary(mnemonicObj.entropy), - 'bip39_checksum': _calculateChecksumBits(mnemonicObj.entropy), - 'bip39_checksum_hex': hex.encode([int.parse(_calculateChecksumBits(mnemonicObj.entropy), radix: 2)]), - }; + final bip39Bin = _bytesToBinary(mnemonicObj.entropy); + final checksumBits = _calculateChecksumBits(mnemonicObj.entropy); - return walletData; - } catch (e) { - if (e.toString().contains('is not in the wordlist')) { - return {'error': 'One or more words are not valid BIP39 words'}; + // Add initialization flags for all available sidechains + final initFlags = {}; + for (final chain in binaryProvider.getL2Chains()) { + if (chain is Sidechain) { + initFlags['sidechain_${chain.slot}_init'] = false; + } } - return {'error': e.toString()}; - } - } - - Future> generateWalletFromEntropy(List entropy, {String? passphrase}) async { - try { - // Create mnemonic from entropy - final mnemonic = Mnemonic(entropy, Language.english); - - // Generate seed using PBKDF2-HMAC-SHA512 - final pbkdf2 = PBKDF2KeyDerivator(HMac(SHA512Digest(), 128)); - final params = Pbkdf2Parameters(utf8.encode('Bitcoin seed'), 2048, 64); - pbkdf2.init(params); - - // Use mnemonic sentence as the input key material - final seedBytes = pbkdf2.process(utf8.encode(mnemonic.sentence + (passphrase ?? ''))); - final seedHex = hex.encode(seedBytes); - - // Create master key from seed - final chain = Chain.seed(seedHex); - final masterKey = chain.forPath('m') as ExtendedPrivateKey; return { - 'mnemonic': mnemonic.sentence, + 'mnemonic': mnemonicObj.sentence, 'seed_hex': seedHex, - 'bip39_binary': _bytesToBinary(mnemonic.entropy), - 'bip39_checksum': _calculateChecksumBits(mnemonic.entropy), - 'bip39_checksum_hex': hex.encode([int.parse(_calculateChecksumBits(mnemonic.entropy), radix: 2)]), - 'master_key': masterKey.privateKeyHex(), - 'chain_code': hex.encode(masterKey.chainCode!), + 'xprv': masterKey.toString(), + 'bip39_bin': bip39Bin, + 'bip39_csum': checksumBits, + 'bip39_csum_hex': hex.encode([int.parse(checksumBits, radix: 2)]), + ...initFlags, }; } catch (e) { return {'error': e.toString()}; @@ -118,7 +92,6 @@ class WalletService extends ChangeNotifier { await _ensureWalletDir(); final walletFile = await _getWalletFile('master_starter.json'); await walletFile.writeAsString(jsonEncode(walletData)); - notifyListeners(); return true; } catch (e) { _logger.e('Error saving wallet: $e'); @@ -131,7 +104,6 @@ class WalletService extends ChangeNotifier { final walletFile = await _getWalletFile('master_starter.json'); if (await walletFile.exists()) { await walletFile.delete(); - notifyListeners(); } return true; } catch (e) { @@ -214,12 +186,17 @@ class WalletService extends ChangeNotifier { // Save to sidechain-specific file await _saveMnemonicStarter('sidechain_${sidechainSlot}_starter.txt', starterData['mnemonic']); + // Mark this sidechain as initialized in master_starter.json + final masterWallet = await loadWallet(); + if (masterWallet != null) { + masterWallet['sidechain_${sidechainSlot}_init'] = true; + await saveWallet(masterWallet); + } + return starterData; } catch (e, stackTrace) { _logger.e('Error deriving sidechain starter: $e\n$stackTrace'); rethrow; - } finally { - notifyListeners(); } } @@ -273,7 +250,6 @@ class WalletService extends ChangeNotifier { await _ensureWalletDir(); final file = await _getWalletFile(fileName); await file.writeAsString(mnemonic); - notifyListeners(); } catch (e, stackTrace) { _logger.e('Error saving starter $fileName: $e\n$stackTrace'); rethrow; @@ -285,7 +261,6 @@ class WalletService extends ChangeNotifier { final file = await _getWalletFile(fileName); if (file.existsSync()) { await file.delete(); - notifyListeners(); } } catch (e, stackTrace) { _logger.e('Error deleting starter $fileName: $e\n$stackTrace'); @@ -341,11 +316,22 @@ class WalletService extends ChangeNotifier { } } } - - // Notify listeners after all starters are generated - notifyListeners(); } catch (e, stack) { _logger.e('Error generating starters for downloaded chains: $e\n$stack'); } } + + Future isSidechainInitialized(int slot) async { + final masterWallet = await loadWallet(); + if (masterWallet == null) return false; + return masterWallet['sidechain_${slot}_init'] ?? false; + } + + Future setSidechainInitialized(int slot) async { + final masterWallet = await loadWallet(); + if (masterWallet == null) return; + + masterWallet['sidechain_${slot}_init'] = true; + await saveWallet(masterWallet); + } } diff --git a/clients/launcher/test/test_utils.dart b/clients/launcher/test/test_utils.dart index be029d60..fa44da14 100644 --- a/clients/launcher/test/test_utils.dart +++ b/clients/launcher/test/test_utils.dart @@ -116,7 +116,7 @@ Future registerTestDependencies() async { if (!GetIt.I.isRegistered()) { GetIt.I.registerLazySingleton( - () => WalletService(), + () => WalletService(GetIt.I.get()), ); } } diff --git a/clients/sail_ui/lib/classes/rpc_connection.dart b/clients/sail_ui/lib/classes/rpc_connection.dart index f349890f..49c281a6 100644 --- a/clients/sail_ui/lib/classes/rpc_connection.dart +++ b/clients/sail_ui/lib/classes/rpc_connection.dart @@ -132,10 +132,21 @@ abstract class RPCConnection extends ChangeNotifier { Future initBinary( BuildContext context, { List? arg, + String? mnemonicPath, }) async { + if (mnemonicPath != null && binary is Sidechain) { + (binary as Sidechain).mnemonicSeedPhrasePath = mnemonicPath; + } + final args = await binaryArgs(conf); args.addAll(arg ?? []); + log.i('init binaries: binary path: ${binary.binary}'); + log.i('init binaries: command line args: ${args.join(" ")}'); + if (binary is Sidechain) { + log.i('init binaries: mnemonic path: ${(binary as Sidechain).mnemonicSeedPhrasePath}'); + } + final processes = GetIt.I.get(); initializingBinary = true; diff --git a/clients/sail_ui/lib/providers/binary_provider.dart b/clients/sail_ui/lib/providers/binary_provider.dart index ce3743f8..f25b848f 100644 --- a/clients/sail_ui/lib/providers/binary_provider.dart +++ b/clients/sail_ui/lib/providers/binary_provider.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; @@ -12,7 +13,6 @@ import 'package:sail_ui/rpcs/bitwindow_api.dart'; import 'package:sail_ui/rpcs/enforcer_rpc.dart'; import 'package:sail_ui/rpcs/mainchain_rpc.dart'; import 'package:sail_ui/rpcs/thunder_rpc.dart'; - /// Represents the current status of a binary download class DownloadState { final double progress; // Only used during installing @@ -168,6 +168,14 @@ class BinaryProvider extends ChangeNotifier { Future startBinary(BuildContext context, Binary binary, {bool useStarter = false}) async { if (!context.mounted) return; + if (useStarter && (binary is Thunder || binary is Bitnames)) { + try { + await _setStarterSeed(binary); + } catch (e) { + log.e('Error setting starter seed: $e'); + } + } + switch (binary) { case ParentChain(): await mainchainRPC.initBinary(context); @@ -179,24 +187,16 @@ class BinaryProvider extends ChangeNotifier { await bitwindowRPC.initBinary(context); case Thunder(): - await thunderRPC.initBinary(context); - if (useStarter) { - try { - await _setStarterSeed(binary); - } catch (e) { - log.e('Error setting starter seed: $e'); - } - } + await thunderRPC.initBinary( + context, + mnemonicPath: (binary as Thunder).mnemonicSeedPhrasePath, + ); case Bitnames(): - await bitnamesRPC.initBinary(context); - if (useStarter) { - try { - await _setStarterSeed(binary); - } catch (e) { - log.e('Error setting starter seed: $e'); - } - } + await bitnamesRPC.initBinary( + context, + mnemonicPath: (binary as Bitnames).mnemonicSeedPhrasePath, + ); default: log.i('is $binary'); @@ -282,10 +282,57 @@ class BinaryProvider extends ChangeNotifier { }; } + Future _isSidechainInitialized(int slot) async { + try { + final masterStarterPath = path.join(appDir.path, 'wallet_starters', 'master_starter.json'); + final masterStarterFile = File(masterStarterPath); + + if (!masterStarterFile.existsSync()) { + return false; + } + + final masterData = jsonDecode(await masterStarterFile.readAsString()) as Map; + return masterData['sidechain_${slot}_init'] ?? false; + } catch (e) { + log.e('Error checking sidechain initialization: $e'); + return false; + } + } + + Future _setSidechainInitialized(int slot) async { + try { + final masterStarterPath = path.join(appDir.path, 'wallet_starters', 'master_starter.json'); + final masterStarterFile = File(masterStarterPath); + + if (!masterStarterFile.existsSync()) { + return; + } + + final masterData = jsonDecode(await masterStarterFile.readAsString()) as Map; + masterData['sidechain_${slot}_init'] = true; + await masterStarterFile.writeAsString(jsonEncode(masterData)); + } catch (e) { + log.e('Error setting sidechain initialization: $e'); + } + } + Future _setStarterSeed(Binary binary) async { if (binary is! Sidechain) return; try { + // Skip if mnemonic path is already set + if (binary.mnemonicSeedPhrasePath != null) { + log.i('Sidechain ${binary.name} already has mnemonic path set, skipping seed setup'); + return; + } + + // Check if this sidechain has already been initialized + final isInitialized = await _isSidechainInitialized(binary.slot); + if (isInitialized) { + log.i('Sidechain ${binary.name} already initialized, skipping seed setup'); + return; + } + final starterDir = path.join(appDir.path, 'wallet_starters'); final starterFile = File( path.join( @@ -302,6 +349,10 @@ class BinaryProvider extends ChangeNotifier { log.i('Found starter file, setting mnemonic seed phrase path'); binary.mnemonicSeedPhrasePath = starterFile.path; log.i('Successfully set mnemonic seed phrase path to: ${starterFile.path}'); + + // Mark this sidechain as initialized + await _setSidechainInitialized(binary.slot); + notifyListeners(); } catch (e, st) { log.e('Error setting starter seed', error: e, stackTrace: st); rethrow; From d3959ef4d8316ab45f18555ab6f62a4bc514fe21 Mon Sep 17 00:00:00 2001 From: Marc Platt Date: Mon, 27 Jan 2025 21:23:39 -0500 Subject: [PATCH 02/12] launcher: fix lint --- clients/launcher/lib/main.dart | 6 +++--- clients/sail_ui/lib/providers/binary_provider.dart | 11 +++++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/clients/launcher/lib/main.dart b/clients/launcher/lib/main.dart index 89a62f53..a3ad4ae5 100644 --- a/clients/launcher/lib/main.dart +++ b/clients/launcher/lib/main.dart @@ -173,9 +173,9 @@ Future initDependencies(Logger log) async { QuotesProvider(prefs), ); - // Register services - GetIt.I.registerLazySingleton( - () => WalletService(), + // Register wallet service + GetIt.I.registerSingleton( + WalletService(binaryProvider), ); } diff --git a/clients/sail_ui/lib/providers/binary_provider.dart b/clients/sail_ui/lib/providers/binary_provider.dart index f25b848f..22837385 100644 --- a/clients/sail_ui/lib/providers/binary_provider.dart +++ b/clients/sail_ui/lib/providers/binary_provider.dart @@ -176,26 +176,33 @@ class BinaryProvider extends ChangeNotifier { } } + if (!context.mounted) return; + switch (binary) { case ParentChain(): + if (!context.mounted) return; await mainchainRPC.initBinary(context); case Enforcer(): + if (!context.mounted) return; await enforcerRPC.initBinary(context); case BitWindow(): + if (!context.mounted) return; await bitwindowRPC.initBinary(context); case Thunder(): + if (!context.mounted) return; await thunderRPC.initBinary( context, - mnemonicPath: (binary as Thunder).mnemonicSeedPhrasePath, + mnemonicPath: binary.mnemonicSeedPhrasePath, ); case Bitnames(): + if (!context.mounted) return; await bitnamesRPC.initBinary( context, - mnemonicPath: (binary as Bitnames).mnemonicSeedPhrasePath, + mnemonicPath: binary.mnemonicSeedPhrasePath, ); default: From d805abe76b24380120b23c75a254cd24c56cd434 Mon Sep 17 00:00:00 2001 From: Marc Platt Date: Mon, 27 Jan 2025 21:31:09 -0500 Subject: [PATCH 03/12] sidesail: configure rpc for init binary with optional path arg --- clients/sidesail/lib/rpc/rpc_ethereum.dart | 9 +++++++++ clients/sidesail/lib/rpc/rpc_testchain.dart | 9 +++++++++ clients/sidesail/lib/rpc/rpc_zcash.dart | 1 + 3 files changed, 19 insertions(+) diff --git a/clients/sidesail/lib/rpc/rpc_ethereum.dart b/clients/sidesail/lib/rpc/rpc_ethereum.dart index d21da6ed..dff794ef 100644 --- a/clients/sidesail/lib/rpc/rpc_ethereum.dart +++ b/clients/sidesail/lib/rpc/rpc_ethereum.dart @@ -194,6 +194,15 @@ class EthereumRPCLive extends EthereumRPC { warnings: [], ); } + + @override + Future initBinary( + BuildContext context, { + List? arg, + String? mnemonicPath, + }) async { + await super.initBinary(context, arg: arg, mnemonicPath: mnemonicPath); + } } /// List of all known RPC methods available / diff --git a/clients/sidesail/lib/rpc/rpc_testchain.dart b/clients/sidesail/lib/rpc/rpc_testchain.dart index 629d8d58..94102109 100644 --- a/clients/sidesail/lib/rpc/rpc_testchain.dart +++ b/clients/sidesail/lib/rpc/rpc_testchain.dart @@ -272,6 +272,15 @@ class TestchainRPCLive extends TestchainRPC { final confirmedFut = await _client().call('getblockchaininfo'); return BlockchainInfo.fromMap(confirmedFut); } + + @override + Future initBinary( + BuildContext context, { + List? arg, + String? mnemonicPath, + }) async { + await super.initBinary(context, arg: arg, mnemonicPath: mnemonicPath); + } } class TestchainRPCError { diff --git a/clients/sidesail/lib/rpc/rpc_zcash.dart b/clients/sidesail/lib/rpc/rpc_zcash.dart index f39c4f77..c08d3acb 100644 --- a/clients/sidesail/lib/rpc/rpc_zcash.dart +++ b/clients/sidesail/lib/rpc/rpc_zcash.dart @@ -96,6 +96,7 @@ abstract class ZCashRPC extends SidechainRPC { Future initBinary( BuildContext context, { List? arg, + String? mnemonicPath, }) async { final args = await binaryArgs(conf); args.addAll(arg ?? []); From 9e49d0df6e9dbd67f7b8dee1ce44abf6f644f2fd Mon Sep 17 00:00:00 2001 From: Marc Platt Date: Mon, 27 Jan 2025 21:34:13 -0500 Subject: [PATCH 04/12] sidesail: missing path import --- clients/sidesail/lib/rpc/rpc_ethereum.dart | 1 + clients/sidesail/lib/rpc/rpc_testchain.dart | 1 + 2 files changed, 2 insertions(+) diff --git a/clients/sidesail/lib/rpc/rpc_ethereum.dart b/clients/sidesail/lib/rpc/rpc_ethereum.dart index dff794ef..cbb87671 100644 --- a/clients/sidesail/lib/rpc/rpc_ethereum.dart +++ b/clients/sidesail/lib/rpc/rpc_ethereum.dart @@ -3,6 +3,7 @@ import 'package:sail_ui/sail_ui.dart'; import 'package:sidesail/rpc/rpc_sidechain.dart'; import 'package:web3dart/json_rpc.dart' as jsonrpc; import 'package:web3dart/web3dart.dart'; +import 'package:flutter/material.dart'; abstract class EthereumRPC extends SidechainRPC { EthereumRPC({ diff --git a/clients/sidesail/lib/rpc/rpc_testchain.dart b/clients/sidesail/lib/rpc/rpc_testchain.dart index 94102109..e81b306b 100644 --- a/clients/sidesail/lib/rpc/rpc_testchain.dart +++ b/clients/sidesail/lib/rpc/rpc_testchain.dart @@ -7,6 +7,7 @@ import 'package:sidesail/rpc/models/bmm_result.dart'; import 'package:sidesail/rpc/models/bundle_info.dart'; import 'package:sidesail/rpc/rpc_sidechain.dart'; import 'package:sidesail/rpc/rpc_withdrawal_bundle.dart'; +import 'package:flutter/material.dart'; /// RPC connection the sidechain node. abstract class TestchainRPC extends SidechainRPC { From 2bd618f88e0c5ef2a480be242082ba7c69e2560b Mon Sep 17 00:00:00 2001 From: Marc Platt Date: Tue, 28 Jan 2025 01:27:07 -0500 Subject: [PATCH 05/12] launcher: implement advanced wallet creator --- .../launcher/lib/services/wallet_service.dart | 45 +++++++++++ .../launcher/lib/widgets/welcome_modal.dart | 81 ++++++++----------- 2 files changed, 80 insertions(+), 46 deletions(-) diff --git a/clients/launcher/lib/services/wallet_service.dart b/clients/launcher/lib/services/wallet_service.dart index 063002b9..03ff73f6 100644 --- a/clients/launcher/lib/services/wallet_service.dart +++ b/clients/launcher/lib/services/wallet_service.dart @@ -59,6 +59,51 @@ class WalletService extends ChangeNotifier { 'bip39_csum': checksumBits, 'bip39_csum_hex': hex.encode([int.parse(checksumBits, radix: 2)]), ...initFlags, + final walletData = { + 'mnemonic': mnemonicObj.sentence, + 'seed_hex': seedHex, + 'master_key': masterKey.privateKeyHex(), + 'chain_code': hex.encode(masterKey.chainCode!), + 'bip39_binary': _bytesToBinary(mnemonicObj.entropy), + 'bip39_checksum': _calculateChecksumBits(mnemonicObj.entropy), + 'bip39_checksum_hex': hex.encode([int.parse(_calculateChecksumBits(mnemonicObj.entropy), radix: 2)]), + }; + + return walletData; + } catch (e) { + if (e.toString().contains('is not in the wordlist')) { + return {'error': 'One or more words are not valid BIP39 words'}; + } + return {'error': e.toString()}; + } + } + + Future> generateWalletFromEntropy(List entropy, {String? passphrase}) async { + try { + // Create mnemonic from entropy + final mnemonic = Mnemonic(entropy, Language.english); + + // Generate seed using PBKDF2-HMAC-SHA512 + final pbkdf2 = PBKDF2KeyDerivator(HMac(SHA512Digest(), 128)); + final params = Pbkdf2Parameters(utf8.encode('Bitcoin seed'), 2048, 64); + pbkdf2.init(params); + + // Use mnemonic sentence as the input key material + final seedBytes = pbkdf2.process(utf8.encode(mnemonic.sentence + (passphrase ?? ''))); + final seedHex = hex.encode(seedBytes); + + // Create master key from seed + final chain = Chain.seed(seedHex); + final masterKey = chain.forPath('m') as ExtendedPrivateKey; + + return { + 'mnemonic': mnemonic.sentence, + 'seed_hex': seedHex, + 'bip39_binary': _bytesToBinary(mnemonic.entropy), + 'bip39_checksum': _calculateChecksumBits(mnemonic.entropy), + 'bip39_checksum_hex': hex.encode([int.parse(_calculateChecksumBits(mnemonic.entropy), radix: 2)]), + 'master_key': masterKey.privateKeyHex(), + 'chain_code': hex.encode(masterKey.chainCode!), }; } catch (e) { return {'error': e.toString()}; diff --git a/clients/launcher/lib/widgets/welcome_modal.dart b/clients/launcher/lib/widgets/welcome_modal.dart index e15da65a..b24f20a8 100644 --- a/clients/launcher/lib/widgets/welcome_modal.dart +++ b/clients/launcher/lib/widgets/welcome_modal.dart @@ -374,7 +374,14 @@ class _WelcomeModalContentState extends State<_WelcomeModalContent> { } final words = _currentWalletData['mnemonic'].split(' '); - final binaryStrings = _currentWalletData['binary_strings'] as List; + final binaryString = _currentWalletData['bip39_binary'] ?? ''; + final binaryStrings = []; + + // Split binary string into 11-bit chunks + for (int i = 0; i < binaryString.length; i += 11) { + final end = i + 11 > binaryString.length ? binaryString.length : i + 11; + binaryStrings.add(binaryString.substring(i, end).padRight(11, '0')); + } return SailRawCard( padding: true, @@ -396,24 +403,10 @@ class _WelcomeModalContentState extends State<_WelcomeModalContent> { children: [ SailText.primary10(words[row * 6 + col], bold: true), if (row * 6 + col < binaryStrings.length) - row * 6 + col == words.length - 1 - ? Row( - mainAxisSize: MainAxisSize.min, - children: [ - SailText.primary10( - binaryStrings[row * 6 + col].substring(0, 7), - color: SailTheme.of(context).colors.textSecondary, - ), - SailText.primary10( - binaryStrings[row * 6 + col].substring(7), - color: SailTheme.of(context).colors.success, - ), - ], - ) - : SailText.primary10( - binaryStrings[row * 6 + col], - color: SailTheme.of(context).colors.textSecondary, - ), + SailText.primary10( + binaryStrings[row * 6 + col], + color: SailTheme.of(context).colors.textSecondary, + ), ], ), ), @@ -430,7 +423,6 @@ class _WelcomeModalContentState extends State<_WelcomeModalContent> { Widget _buildInfoPanel() { final theme = SailTheme.of(context); - final binaryString = _currentWalletData['bip39_binary'] as String?; return SailRawCard( padding: true, @@ -449,12 +441,34 @@ class _WelcomeModalContentState extends State<_WelcomeModalContent> { ), Expanded( child: SailText.primary10( - binaryString ?? '', + _currentWalletData['bip39_binary'] ?? '', color: theme.colors.textSecondary, ), ), ], ), + // BIP39 Checksum + SailRow( + spacing: SailStyleValues.padding08, + children: [ + SizedBox( + width: 100, + child: SailText.primary10('Checksum:', bold: true), + ), + SailText.primary10( + _currentWalletData['bip39_checksum'] ?? '', + color: theme.colors.textSecondary, + ), + const SizedBox(width: SailStyleValues.padding16), + SailText.primary10('Hex:', bold: true), + const SizedBox(width: SailStyleValues.padding04), + SailText.primary10( + _currentWalletData['bip39_checksum_hex'] ?? '', + color: theme.colors.textSecondary, + ), + Expanded(child: Container()), + ], + ), // Master Key SailRow( spacing: SailStyleValues.padding08, @@ -707,31 +721,6 @@ class _WelcomeModalContentState extends State<_WelcomeModalContent> { return; } - // Process binary representation - final binaryString = wallet['bip39_binary'] as String; - final words = (wallet['mnemonic'] as String).split(' '); - final binaryStrings = []; - - // For 12 words: 11 words × 11 bits + 7 bits + 4 checksum bits = 132 bits - final checksumBits = wallet['bip39_checksum'] as String; - final entropyBits = binaryString.substring(0, binaryString.length - checksumBits.length); - - // Split into 11-bit chunks for each word - for (int i = 0; i < words.length - 1; i++) { - final start = i * 11; - final end = start + 11; - final chunk = entropyBits.substring(start, end); - binaryStrings.add(chunk); - } - - // Handle last word specially (7 bits entropy + 4 bits checksum) - final lastEntropyBits = entropyBits.substring(entropyBits.length - 7); - final lastWord = lastEntropyBits + checksumBits; - binaryStrings.add(lastWord); - - // Update wallet data with processed binary strings - wallet['binary_strings'] = binaryStrings; - setState(() { _currentWalletData = Map.from(wallet); _isValidInput = true; From fb61b994ff1c6985f8a4d1516002b220ee97b358 Mon Sep 17 00:00:00 2001 From: Marc Platt Date: Mon, 27 Jan 2025 21:15:45 -0500 Subject: [PATCH 06/12] launcher: restore starter functionality to launch with l2 --- .../launcher/lib/services/wallet_service.dart | 21 ++++++++++++++----- .../sail_ui/lib/classes/rpc_connection.dart | 2 +- .../lib/providers/binary_provider.dart | 12 +++++------ 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/clients/launcher/lib/services/wallet_service.dart b/clients/launcher/lib/services/wallet_service.dart index 03ff73f6..25f51ce1 100644 --- a/clients/launcher/lib/services/wallet_service.dart +++ b/clients/launcher/lib/services/wallet_service.dart @@ -96,14 +96,25 @@ class WalletService extends ChangeNotifier { final chain = Chain.seed(seedHex); final masterKey = chain.forPath('m') as ExtendedPrivateKey; + final bip39Bin = _bytesToBinary(mnemonic.entropy); + final checksumBits = _calculateChecksumBits(mnemonic.entropy); + + // Add initialization flags for all available sidechains + final initFlags = {}; + for (final chain in binaryProvider.getL2Chains()) { + if (chain is Sidechain) { + initFlags['sidechain_${chain.slot}_init'] = false; + } + } + return { 'mnemonic': mnemonic.sentence, 'seed_hex': seedHex, - 'bip39_binary': _bytesToBinary(mnemonic.entropy), - 'bip39_checksum': _calculateChecksumBits(mnemonic.entropy), - 'bip39_checksum_hex': hex.encode([int.parse(_calculateChecksumBits(mnemonic.entropy), radix: 2)]), - 'master_key': masterKey.privateKeyHex(), - 'chain_code': hex.encode(masterKey.chainCode!), + 'xprv': masterKey.toString(), + 'bip39_bin': bip39Bin, + 'bip39_csum': checksumBits, + 'bip39_csum_hex': hex.encode([int.parse(checksumBits, radix: 2)]), + ...initFlags, }; } catch (e) { return {'error': e.toString()}; diff --git a/clients/sail_ui/lib/classes/rpc_connection.dart b/clients/sail_ui/lib/classes/rpc_connection.dart index 49c281a6..2ff7b8c7 100644 --- a/clients/sail_ui/lib/classes/rpc_connection.dart +++ b/clients/sail_ui/lib/classes/rpc_connection.dart @@ -144,7 +144,7 @@ abstract class RPCConnection extends ChangeNotifier { log.i('init binaries: binary path: ${binary.binary}'); log.i('init binaries: command line args: ${args.join(" ")}'); if (binary is Sidechain) { - log.i('init binaries: mnemonic path: ${(binary as Sidechain).mnemonicSeedPhrasePath}'); + log.i('init binaries: mnemonic path: ${binary.mnemonicSeedPhrasePath}'); } final processes = GetIt.I.get(); diff --git a/clients/sail_ui/lib/providers/binary_provider.dart b/clients/sail_ui/lib/providers/binary_provider.dart index 22837385..b6d81503 100644 --- a/clients/sail_ui/lib/providers/binary_provider.dart +++ b/clients/sail_ui/lib/providers/binary_provider.dart @@ -176,8 +176,6 @@ class BinaryProvider extends ChangeNotifier { } } - if (!context.mounted) return; - switch (binary) { case ParentChain(): if (!context.mounted) return; @@ -192,17 +190,19 @@ class BinaryProvider extends ChangeNotifier { await bitwindowRPC.initBinary(context); case Thunder(): - if (!context.mounted) return; await thunderRPC.initBinary( context, - mnemonicPath: binary.mnemonicSeedPhrasePath, + arg: binary.mnemonicSeedPhrasePath != null + ? ['--mnemonic-seed-phrase-path', binary.mnemonicSeedPhrasePath!] + : null, ); case Bitnames(): - if (!context.mounted) return; await bitnamesRPC.initBinary( context, - mnemonicPath: binary.mnemonicSeedPhrasePath, + arg: binary.mnemonicSeedPhrasePath != null + ? ['--mnemonic-seed-phrase-path', binary.mnemonicSeedPhrasePath!] + : null, ); default: From 1148eaf87ffcf8654f626a51effa748fd942e296 Mon Sep 17 00:00:00 2001 From: Marc Platt Date: Mon, 27 Jan 2025 21:23:39 -0500 Subject: [PATCH 07/12] launcher: fix lint --- clients/sail_ui/lib/providers/binary_provider.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/clients/sail_ui/lib/providers/binary_provider.dart b/clients/sail_ui/lib/providers/binary_provider.dart index b6d81503..22837385 100644 --- a/clients/sail_ui/lib/providers/binary_provider.dart +++ b/clients/sail_ui/lib/providers/binary_provider.dart @@ -176,6 +176,8 @@ class BinaryProvider extends ChangeNotifier { } } + if (!context.mounted) return; + switch (binary) { case ParentChain(): if (!context.mounted) return; @@ -190,19 +192,17 @@ class BinaryProvider extends ChangeNotifier { await bitwindowRPC.initBinary(context); case Thunder(): + if (!context.mounted) return; await thunderRPC.initBinary( context, - arg: binary.mnemonicSeedPhrasePath != null - ? ['--mnemonic-seed-phrase-path', binary.mnemonicSeedPhrasePath!] - : null, + mnemonicPath: binary.mnemonicSeedPhrasePath, ); case Bitnames(): + if (!context.mounted) return; await bitnamesRPC.initBinary( context, - arg: binary.mnemonicSeedPhrasePath != null - ? ['--mnemonic-seed-phrase-path', binary.mnemonicSeedPhrasePath!] - : null, + mnemonicPath: binary.mnemonicSeedPhrasePath, ); default: From d334861384d34e34de527cd16bbd1cf2acfdeaa4 Mon Sep 17 00:00:00 2001 From: Marc Platt Date: Tue, 28 Jan 2025 10:47:09 -0500 Subject: [PATCH 08/12] launcher: rebase and address comments --- clients/sail_ui/lib/classes/rpc_connection.dart | 6 ------ .../sail_ui/lib/providers/binary_provider.dart | 15 ++++++--------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/clients/sail_ui/lib/classes/rpc_connection.dart b/clients/sail_ui/lib/classes/rpc_connection.dart index 2ff7b8c7..d5259e38 100644 --- a/clients/sail_ui/lib/classes/rpc_connection.dart +++ b/clients/sail_ui/lib/classes/rpc_connection.dart @@ -141,12 +141,6 @@ abstract class RPCConnection extends ChangeNotifier { final args = await binaryArgs(conf); args.addAll(arg ?? []); - log.i('init binaries: binary path: ${binary.binary}'); - log.i('init binaries: command line args: ${args.join(" ")}'); - if (binary is Sidechain) { - log.i('init binaries: mnemonic path: ${binary.mnemonicSeedPhrasePath}'); - } - final processes = GetIt.I.get(); initializingBinary = true; diff --git a/clients/sail_ui/lib/providers/binary_provider.dart b/clients/sail_ui/lib/providers/binary_provider.dart index 22837385..1914ab9f 100644 --- a/clients/sail_ui/lib/providers/binary_provider.dart +++ b/clients/sail_ui/lib/providers/binary_provider.dart @@ -176,33 +176,30 @@ class BinaryProvider extends ChangeNotifier { } } - if (!context.mounted) return; - switch (binary) { case ParentChain(): - if (!context.mounted) return; await mainchainRPC.initBinary(context); case Enforcer(): - if (!context.mounted) return; await enforcerRPC.initBinary(context); case BitWindow(): - if (!context.mounted) return; await bitwindowRPC.initBinary(context); case Thunder(): - if (!context.mounted) return; await thunderRPC.initBinary( context, - mnemonicPath: binary.mnemonicSeedPhrasePath, + arg: binary.mnemonicSeedPhrasePath != null + ? ['--mnemonic-seed-phrase-path', binary.mnemonicSeedPhrasePath!] + : null, ); case Bitnames(): - if (!context.mounted) return; await bitnamesRPC.initBinary( context, - mnemonicPath: binary.mnemonicSeedPhrasePath, + arg: binary.mnemonicSeedPhrasePath != null + ? ['--mnemonic-seed-phrase-path', binary.mnemonicSeedPhrasePath!] + : null, ); default: From 38e401a90062e0fc4867af5e6b86e21dcef33441 Mon Sep 17 00:00:00 2001 From: Marc Platt Date: Tue, 28 Jan 2025 11:21:09 -0500 Subject: [PATCH 09/12] launcher: continue rebase and starter debug --- clients/launcher/lib/main.dart | 4 +- .../launcher/lib/services/wallet_service.dart | 78 +++++-------------- .../sail_ui/lib/classes/rpc_connection.dart | 6 +- .../lib/providers/binary_provider.dart | 12 +-- 4 files changed, 30 insertions(+), 70 deletions(-) diff --git a/clients/launcher/lib/main.dart b/clients/launcher/lib/main.dart index a3ad4ae5..209457b8 100644 --- a/clients/launcher/lib/main.dart +++ b/clients/launcher/lib/main.dart @@ -174,9 +174,7 @@ Future initDependencies(Logger log) async { ); // Register wallet service - GetIt.I.registerSingleton( - WalletService(binaryProvider), - ); + GetIt.I.registerSingleton(WalletService()); } Future> _loadBinaries(Directory appDir) async { diff --git a/clients/launcher/lib/services/wallet_service.dart b/clients/launcher/lib/services/wallet_service.dart index 25f51ce1..49039a16 100644 --- a/clients/launcher/lib/services/wallet_service.dart +++ b/clients/launcher/lib/services/wallet_service.dart @@ -6,6 +6,7 @@ import 'package:convert/convert.dart'; import 'package:crypto/crypto.dart'; import 'package:dart_bip32_bip44/dart_bip32_bip44.dart'; import 'package:flutter/foundation.dart'; +import 'package:get_it/get_it.dart'; import 'package:launcher/env.dart'; import 'package:logger/logger.dart'; import 'package:path/path.dart' as path; @@ -17,12 +18,11 @@ import 'package:pointycastle/macs/hmac.dart'; import 'package:pointycastle/digests/sha512.dart'; class WalletService extends ChangeNotifier { - final BinaryProvider binaryProvider; + BinaryProvider get binaryProvider => GetIt.I.get(); + final _logger = Logger(); static const String defaultBip32Path = "m/44'/0'/0'"; - WalletService(this.binaryProvider); - Future hasExistingWallet() async { final walletFile = await _getWalletFile('master_starter.json'); return walletFile.existsSync(); @@ -40,25 +40,6 @@ class WalletService extends ChangeNotifier { final chain = Chain.seed(seedHex); final masterKey = chain.forPath('m') as ExtendedPrivateKey; - final bip39Bin = _bytesToBinary(mnemonicObj.entropy); - final checksumBits = _calculateChecksumBits(mnemonicObj.entropy); - - // Add initialization flags for all available sidechains - final initFlags = {}; - for (final chain in binaryProvider.getL2Chains()) { - if (chain is Sidechain) { - initFlags['sidechain_${chain.slot}_init'] = false; - } - } - - return { - 'mnemonic': mnemonicObj.sentence, - 'seed_hex': seedHex, - 'xprv': masterKey.toString(), - 'bip39_bin': bip39Bin, - 'bip39_csum': checksumBits, - 'bip39_csum_hex': hex.encode([int.parse(checksumBits, radix: 2)]), - ...initFlags, final walletData = { 'mnemonic': mnemonicObj.sentence, 'seed_hex': seedHex, @@ -96,25 +77,14 @@ class WalletService extends ChangeNotifier { final chain = Chain.seed(seedHex); final masterKey = chain.forPath('m') as ExtendedPrivateKey; - final bip39Bin = _bytesToBinary(mnemonic.entropy); - final checksumBits = _calculateChecksumBits(mnemonic.entropy); - - // Add initialization flags for all available sidechains - final initFlags = {}; - for (final chain in binaryProvider.getL2Chains()) { - if (chain is Sidechain) { - initFlags['sidechain_${chain.slot}_init'] = false; - } - } - return { 'mnemonic': mnemonic.sentence, 'seed_hex': seedHex, - 'xprv': masterKey.toString(), - 'bip39_bin': bip39Bin, - 'bip39_csum': checksumBits, - 'bip39_csum_hex': hex.encode([int.parse(checksumBits, radix: 2)]), - ...initFlags, + 'bip39_binary': _bytesToBinary(mnemonic.entropy), + 'bip39_checksum': _calculateChecksumBits(mnemonic.entropy), + 'bip39_checksum_hex': hex.encode([int.parse(_calculateChecksumBits(mnemonic.entropy), radix: 2)]), + 'master_key': masterKey.privateKeyHex(), + 'chain_code': hex.encode(masterKey.chainCode!), }; } catch (e) { return {'error': e.toString()}; @@ -148,6 +118,7 @@ class WalletService extends ChangeNotifier { await _ensureWalletDir(); final walletFile = await _getWalletFile('master_starter.json'); await walletFile.writeAsString(jsonEncode(walletData)); + notifyListeners(); return true; } catch (e) { _logger.e('Error saving wallet: $e'); @@ -160,6 +131,7 @@ class WalletService extends ChangeNotifier { final walletFile = await _getWalletFile('master_starter.json'); if (await walletFile.exists()) { await walletFile.delete(); + notifyListeners(); } return true; } catch (e) { @@ -242,17 +214,12 @@ class WalletService extends ChangeNotifier { // Save to sidechain-specific file await _saveMnemonicStarter('sidechain_${sidechainSlot}_starter.txt', starterData['mnemonic']); - // Mark this sidechain as initialized in master_starter.json - final masterWallet = await loadWallet(); - if (masterWallet != null) { - masterWallet['sidechain_${sidechainSlot}_init'] = true; - await saveWallet(masterWallet); - } - return starterData; } catch (e, stackTrace) { _logger.e('Error deriving sidechain starter: $e\n$stackTrace'); rethrow; + } finally { + notifyListeners(); } } @@ -306,6 +273,7 @@ class WalletService extends ChangeNotifier { await _ensureWalletDir(); final file = await _getWalletFile(fileName); await file.writeAsString(mnemonic); + notifyListeners(); } catch (e, stackTrace) { _logger.e('Error saving starter $fileName: $e\n$stackTrace'); rethrow; @@ -317,6 +285,7 @@ class WalletService extends ChangeNotifier { final file = await _getWalletFile(fileName); if (file.existsSync()) { await file.delete(); + notifyListeners(); } } catch (e, stackTrace) { _logger.e('Error deleting starter $fileName: $e\n$stackTrace'); @@ -372,22 +341,11 @@ class WalletService extends ChangeNotifier { } } } + + // Notify listeners after all starters are generated + notifyListeners(); } catch (e, stack) { _logger.e('Error generating starters for downloaded chains: $e\n$stack'); } } - - Future isSidechainInitialized(int slot) async { - final masterWallet = await loadWallet(); - if (masterWallet == null) return false; - return masterWallet['sidechain_${slot}_init'] ?? false; - } - - Future setSidechainInitialized(int slot) async { - final masterWallet = await loadWallet(); - if (masterWallet == null) return; - - masterWallet['sidechain_${slot}_init'] = true; - await saveWallet(masterWallet); - } -} +} \ No newline at end of file diff --git a/clients/sail_ui/lib/classes/rpc_connection.dart b/clients/sail_ui/lib/classes/rpc_connection.dart index d5259e38..3929d2a0 100644 --- a/clients/sail_ui/lib/classes/rpc_connection.dart +++ b/clients/sail_ui/lib/classes/rpc_connection.dart @@ -135,7 +135,11 @@ abstract class RPCConnection extends ChangeNotifier { String? mnemonicPath, }) async { if (mnemonicPath != null && binary is Sidechain) { - (binary as Sidechain).mnemonicSeedPhrasePath = mnemonicPath; + final sidechain = binary as Sidechain; + // Only set the mnemonic path if it's not already set + if (sidechain.mnemonicSeedPhrasePath == null) { + sidechain.mnemonicSeedPhrasePath = mnemonicPath; + } } final args = await binaryArgs(conf); diff --git a/clients/sail_ui/lib/providers/binary_provider.dart b/clients/sail_ui/lib/providers/binary_provider.dart index 1914ab9f..a78a1914 100644 --- a/clients/sail_ui/lib/providers/binary_provider.dart +++ b/clients/sail_ui/lib/providers/binary_provider.dart @@ -324,12 +324,6 @@ class BinaryProvider extends ChangeNotifier { if (binary is! Sidechain) return; try { - // Skip if mnemonic path is already set - if (binary.mnemonicSeedPhrasePath != null) { - log.i('Sidechain ${binary.name} already has mnemonic path set, skipping seed setup'); - return; - } - // Check if this sidechain has already been initialized final isInitialized = await _isSidechainInitialized(binary.slot); if (isInitialized) { @@ -337,6 +331,12 @@ class BinaryProvider extends ChangeNotifier { return; } + // Skip if mnemonic path is already set + if (binary.mnemonicSeedPhrasePath != null) { + log.i('Sidechain ${binary.name} already has mnemonic path set, skipping seed setup'); + return; + } + final starterDir = path.join(appDir.path, 'wallet_starters'); final starterFile = File( path.join( From 21e25ab7bb9d43c7930326d8b9e6a6384b0a4c3a Mon Sep 17 00:00:00 2001 From: Marc Platt Date: Tue, 28 Jan 2025 12:44:16 -0500 Subject: [PATCH 10/12] launcher: reconfigure init binary and add check to start l2 --- clients/launcher/test/test_utils.dart | 2 +- .../sail_ui/lib/classes/rpc_connection.dart | 9 ----- .../lib/providers/binary_provider.dart | 34 ++++++++++++------- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/clients/launcher/test/test_utils.dart b/clients/launcher/test/test_utils.dart index fa44da14..be029d60 100644 --- a/clients/launcher/test/test_utils.dart +++ b/clients/launcher/test/test_utils.dart @@ -116,7 +116,7 @@ Future registerTestDependencies() async { if (!GetIt.I.isRegistered()) { GetIt.I.registerLazySingleton( - () => WalletService(GetIt.I.get()), + () => WalletService(), ); } } diff --git a/clients/sail_ui/lib/classes/rpc_connection.dart b/clients/sail_ui/lib/classes/rpc_connection.dart index 3929d2a0..f349890f 100644 --- a/clients/sail_ui/lib/classes/rpc_connection.dart +++ b/clients/sail_ui/lib/classes/rpc_connection.dart @@ -132,16 +132,7 @@ abstract class RPCConnection extends ChangeNotifier { Future initBinary( BuildContext context, { List? arg, - String? mnemonicPath, }) async { - if (mnemonicPath != null && binary is Sidechain) { - final sidechain = binary as Sidechain; - // Only set the mnemonic path if it's not already set - if (sidechain.mnemonicSeedPhrasePath == null) { - sidechain.mnemonicSeedPhrasePath = mnemonicPath; - } - } - final args = await binaryArgs(conf); args.addAll(arg ?? []); diff --git a/clients/sail_ui/lib/providers/binary_provider.dart b/clients/sail_ui/lib/providers/binary_provider.dart index a78a1914..1e91dc4a 100644 --- a/clients/sail_ui/lib/providers/binary_provider.dart +++ b/clients/sail_ui/lib/providers/binary_provider.dart @@ -164,10 +164,32 @@ class BinaryProvider extends ChangeNotifier { _statusController.add(Map.from(_downloadStates)); } + /// Check if a binary can be started based on its dependencies + String? canStart(Binary binary) { + final mainchainReady = mainchainConnected && !inIBD; + return switch (binary) { + Enforcer() => + mainchainReady ? null : 'Mainchain must be started and fully synced before starting Enforcer', + BitWindow() => + enforcerConnected && mainchainReady ? null : 'Mainchain and Enforcer must be running and fully synced before starting BitWindow', + Thunder() => + enforcerConnected && mainchainReady ? null : 'Mainchain and Enforcer must be running and fully synced before starting Thunder', + Bitnames() => + enforcerConnected && mainchainReady ? null : 'Mainchain and Enforcer must be running and fully synced before starting Bitnames', + _ => null, // No requirements for mainchain + }; + } + // Start a binary, and set starter seeds (if set) Future startBinary(BuildContext context, Binary binary, {bool useStarter = false}) async { if (!context.mounted) return; + final startError = canStart(binary); + if (startError != null) { + log.e('Cannot start ${binary.name}: $startError'); + throw Exception(startError); + } + if (useStarter && (binary is Thunder || binary is Bitnames)) { try { await _setStarterSeed(binary); @@ -274,18 +296,6 @@ class BinaryProvider extends ChangeNotifier { await downloadsDir.delete(recursive: true); } - /// Check if a binary can be started based on its dependencies - String? canStart(Binary binary) { - return switch (binary) { - Enforcer() => - mainchainConnected && !inIBD ? null : 'Mainchain must be started and fully synced before starting Enforcer', - BitWindow() => enforcerConnected ? null : 'Enforcer must be running and fully synced before starting BitWindow', - Thunder() => enforcerConnected ? null : 'Enforcer must be running and fully synced before starting Thunder', - Bitnames() => enforcerConnected ? null : 'Enforcer must be running and fully synced before starting Bitnames', - _ => null, // No requirements for mainchain - }; - } - Future _isSidechainInitialized(int slot) async { try { final masterStarterPath = path.join(appDir.path, 'wallet_starters', 'master_starter.json'); From 96372917db3ee35407a9df9588e2616f26c3e255 Mon Sep 17 00:00:00 2001 From: Marc Platt Date: Tue, 28 Jan 2025 12:47:24 -0500 Subject: [PATCH 11/12] sidesail: remove redundant mnemonic arg --- clients/sidesail/lib/rpc/rpc_ethereum.dart | 3 +-- clients/sidesail/lib/rpc/rpc_testchain.dart | 3 +-- clients/sidesail/lib/rpc/rpc_zcash.dart | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/clients/sidesail/lib/rpc/rpc_ethereum.dart b/clients/sidesail/lib/rpc/rpc_ethereum.dart index cbb87671..f0bc26e2 100644 --- a/clients/sidesail/lib/rpc/rpc_ethereum.dart +++ b/clients/sidesail/lib/rpc/rpc_ethereum.dart @@ -200,9 +200,8 @@ class EthereumRPCLive extends EthereumRPC { Future initBinary( BuildContext context, { List? arg, - String? mnemonicPath, }) async { - await super.initBinary(context, arg: arg, mnemonicPath: mnemonicPath); + await super.initBinary(context, arg: arg); } } diff --git a/clients/sidesail/lib/rpc/rpc_testchain.dart b/clients/sidesail/lib/rpc/rpc_testchain.dart index e81b306b..be965bd6 100644 --- a/clients/sidesail/lib/rpc/rpc_testchain.dart +++ b/clients/sidesail/lib/rpc/rpc_testchain.dart @@ -278,9 +278,8 @@ class TestchainRPCLive extends TestchainRPC { Future initBinary( BuildContext context, { List? arg, - String? mnemonicPath, }) async { - await super.initBinary(context, arg: arg, mnemonicPath: mnemonicPath); + await super.initBinary(context, arg: arg); } } diff --git a/clients/sidesail/lib/rpc/rpc_zcash.dart b/clients/sidesail/lib/rpc/rpc_zcash.dart index c08d3acb..f39c4f77 100644 --- a/clients/sidesail/lib/rpc/rpc_zcash.dart +++ b/clients/sidesail/lib/rpc/rpc_zcash.dart @@ -96,7 +96,6 @@ abstract class ZCashRPC extends SidechainRPC { Future initBinary( BuildContext context, { List? arg, - String? mnemonicPath, }) async { final args = await binaryArgs(conf); args.addAll(arg ?? []); From 17ec9df92e53d16de752739c9cacb2cb8585a54d Mon Sep 17 00:00:00 2001 From: Marc Platt Date: Tue, 28 Jan 2025 13:45:19 -0500 Subject: [PATCH 12/12] launcher: better advanced mode --- .../launcher/lib/widgets/welcome_modal.dart | 72 +++++++++++++++---- 1 file changed, 60 insertions(+), 12 deletions(-) diff --git a/clients/launcher/lib/widgets/welcome_modal.dart b/clients/launcher/lib/widgets/welcome_modal.dart index b24f20a8..cc387d5a 100644 --- a/clients/launcher/lib/widgets/welcome_modal.dart +++ b/clients/launcher/lib/widgets/welcome_modal.dart @@ -373,21 +373,44 @@ class _WelcomeModalContentState extends State<_WelcomeModalContent> { return const SizedBox.shrink(); } + final theme = SailTheme.of(context); final words = _currentWalletData['mnemonic'].split(' '); final binaryString = _currentWalletData['bip39_binary'] ?? ''; + final checksumBinary = _currentWalletData['bip39_checksum'] ?? ''; + + // Calculate total length for verification + final entropyBits = binaryString.length; + final checksumBits = entropyBits ~/ 32; // BIP39 spec: checksum length = entropy length / 32 + final totalBits = entropyBits + checksumBits; + final expectedWords = totalBits ~/ 11; // Each word represents 11 bits + + if (words.length != expectedWords) { + return const SizedBox.shrink(); // Invalid state + } + + final fullBinary = binaryString + checksumBinary; final binaryStrings = []; - // Split binary string into 11-bit chunks - for (int i = 0; i < binaryString.length; i += 11) { - final end = i + 11 > binaryString.length ? binaryString.length : i + 11; - binaryStrings.add(binaryString.substring(i, end).padRight(11, '0')); + // Split into 11-bit chunks as per BIP39 + for (int i = 0; i < fullBinary.length; i += 11) { + final end = i + 11 > fullBinary.length ? fullBinary.length : i + 11; + final chunk = fullBinary.substring(i, end).padRight(11, '0'); + binaryStrings.add(chunk); } return SailRawCard( padding: true, secondary: true, child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ + Padding( + padding: const EdgeInsets.only(bottom: 8), + child: SailText.primary10( + 'Entropy (${entropyBits} bits) + Checksum (${checksumBits} bits) = ${totalBits} bits ÷ 11 = ${expectedWords} words', + color: theme.colors.textSecondary, + ), + ), for (int row = 0; row < (words.length ~/ 6); row++) Padding( padding: EdgeInsets.only(bottom: row < (words.length ~/ 6) - 1 ? 4 : 0), @@ -403,10 +426,33 @@ class _WelcomeModalContentState extends State<_WelcomeModalContent> { children: [ SailText.primary10(words[row * 6 + col], bold: true), if (row * 6 + col < binaryStrings.length) - SailText.primary10( - binaryStrings[row * 6 + col], - color: SailTheme.of(context).colors.textSecondary, - ), + row * 6 + col == words.length - 1 + ? RichText( + text: TextSpan( + children: [ + TextSpan( + text: binaryStrings[row * 6 + col].substring(0, 7), + style: TextStyle( + fontSize: 10, + color: theme.colors.textSecondary, + fontFamily: 'IBM Plex Mono', + ), + ), + TextSpan( + text: binaryStrings[row * 6 + col].substring(7), + style: TextStyle( + fontSize: 10, + color: theme.colors.success, + fontFamily: 'IBM Plex Mono', + ), + ), + ], + ), + ) + : SailText.primary10( + binaryStrings[row * 6 + col], + color: theme.colors.textSecondary, + ), ], ), ), @@ -423,6 +469,8 @@ class _WelcomeModalContentState extends State<_WelcomeModalContent> { Widget _buildInfoPanel() { final theme = SailTheme.of(context); + final binaryString = _currentWalletData['bip39_binary'] ?? ''; + final checksumBinary = _currentWalletData['bip39_checksum'] ?? ''; return SailRawCard( padding: true, @@ -441,7 +489,7 @@ class _WelcomeModalContentState extends State<_WelcomeModalContent> { ), Expanded( child: SailText.primary10( - _currentWalletData['bip39_binary'] ?? '', + binaryString, color: theme.colors.textSecondary, ), ), @@ -456,15 +504,15 @@ class _WelcomeModalContentState extends State<_WelcomeModalContent> { child: SailText.primary10('Checksum:', bold: true), ), SailText.primary10( - _currentWalletData['bip39_checksum'] ?? '', - color: theme.colors.textSecondary, + checksumBinary, + color: theme.colors.success, ), const SizedBox(width: SailStyleValues.padding16), SailText.primary10('Hex:', bold: true), const SizedBox(width: SailStyleValues.padding04), SailText.primary10( _currentWalletData['bip39_checksum_hex'] ?? '', - color: theme.colors.textSecondary, + color: theme.colors.success, ), Expanded(child: Container()), ],