Merge pull request #1041 from cypherstack/frost-init-fix

Various fixes and tweaks
This commit is contained in:
julian-CStack 2024-12-03 16:24:21 -06:00 committed by GitHub
commit a74c565f9a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 314 additions and 252 deletions

View file

@ -26,18 +26,20 @@ import '../../../services/transaction_notification_tracker.dart';
import '../../../themes/stack_colors.dart'; import '../../../themes/stack_colors.dart';
import '../../../utilities/assets.dart'; import '../../../utilities/assets.dart';
import '../../../utilities/logger.dart'; import '../../../utilities/logger.dart';
import '../../../utilities/show_loading.dart';
import '../../../utilities/text_styles.dart'; import '../../../utilities/text_styles.dart';
import '../../../utilities/util.dart'; import '../../../utilities/util.dart';
import '../../../wallets/crypto_currency/crypto_currency.dart'; import '../../../wallets/crypto_currency/crypto_currency.dart';
import '../../../wallets/isar/models/wallet_info.dart'; import '../../../wallets/isar/models/wallet_info.dart';
import '../../../wallets/wallet/intermediate/lib_monero_wallet.dart';
import '../../../wallets/wallet/wallet.dart'; import '../../../wallets/wallet/wallet.dart';
import '../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart';
import '../../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../../widgets/desktop/desktop_app_bar.dart'; import '../../../widgets/desktop/desktop_app_bar.dart';
import '../../../widgets/desktop/desktop_scaffold.dart'; import '../../../widgets/desktop/desktop_scaffold.dart';
import '../../../widgets/loading_indicator.dart';
import '../../../widgets/rounded_container.dart'; import '../../../widgets/rounded_container.dart';
import '../../../widgets/rounded_white_container.dart'; import '../../../widgets/rounded_white_container.dart';
import '../../../widgets/stack_dialog.dart';
import '../new_wallet_options/new_wallet_options_view.dart'; import '../new_wallet_options/new_wallet_options_view.dart';
import '../new_wallet_recovery_phrase_view/new_wallet_recovery_phrase_view.dart'; import '../new_wallet_recovery_phrase_view/new_wallet_recovery_phrase_view.dart';
import 'recovery_phrase_explanation_dialog.dart'; import 'recovery_phrase_explanation_dialog.dart';
@ -65,6 +67,221 @@ class _NewWalletRecoveryPhraseWarningViewState
late final String walletName; late final String walletName;
late final bool isDesktop; late final bool isDesktop;
Future<void> _initNewWallet() async {
Exception? ex;
final result = await showLoading(
whileFuture: _initNewFuture(),
context: context,
message: "Generating...",
onException: (e) => ex = e,
);
// on failure show error message
if (result == null) {
if (mounted) {
await showDialog<void>(
context: context,
builder: (_) => StackOkDialog(
title: "Create Wallet Error",
message: ex?.toString() ?? "Unknown error",
maxWidth: 600,
),
);
}
return;
} else {
if (mounted) {
final nav = Navigator.of(context);
unawaited(
nav.pushNamed(
NewWalletRecoveryPhraseView.routeName,
arguments: Tuple2(
result.$1,
result.$2,
),
),
);
}
}
}
Future<(Wallet, List<String>)> _initNewFuture() async {
try {
String? otherDataJsonString;
if (widget.coin is Tezos) {
otherDataJsonString = jsonEncode({
WalletInfoKeys.tezosDerivationPath:
Tezos.standardDerivationPath.value,
});
// }//todo: probably not needed (broken anyways)
// else if (widget.coin is Epiccash) {
// final int secondsSinceEpoch =
// DateTime.now().millisecondsSinceEpoch ~/ 1000;
// const int epicCashFirstBlock = 1565370278;
// const double overestimateSecondsPerBlock = 61;
// int chosenSeconds = secondsSinceEpoch - epicCashFirstBlock;
// int approximateHeight = chosenSeconds ~/ overestimateSecondsPerBlock;
// /
// // debugPrint(
// // "approximate height: $approximateHeight chosen_seconds: $chosenSeconds");
// height = approximateHeight;
// if (height < 0) {
// height = 0;
// }
//
// otherDataJsonString = jsonEncode(
// {
// WalletInfoKeys.epiccashData: jsonEncode(
// ExtraEpiccashWalletInfo(
// receivingIndex: 0,
// changeIndex: 0,
// slatesToAddresses: {},
// slatesToCommits: {},
// lastScannedBlock: epicCashFirstBlock,
// restoreHeight: height,
// creationHeight: height,
// ).toMap(),
// ),
// },
// );
} else if (widget.coin is Firo) {
otherDataJsonString = jsonEncode(
{
WalletInfoKeys.lelantusCoinIsarRescanRequired: false,
},
);
}
final info = WalletInfo.createNew(
coin: widget.coin,
name: widget.walletName,
otherDataJsonString: otherDataJsonString,
);
var node = ref
.read(
nodeServiceChangeNotifierProvider,
)
.getPrimaryNodeFor(
currency: coin,
);
if (node == null) {
node = coin.defaultNode;
await ref
.read(
nodeServiceChangeNotifierProvider,
)
.setPrimaryNodeFor(
coin: coin,
node: node,
);
}
final txTracker = TransactionNotificationTracker(
walletId: info.walletId,
);
String? mnemonicPassphrase;
String? mnemonic;
String? privateKey;
// set some sane default
int wordCount = info.coin.defaultSeedPhraseLength;
// TODO: Refactor these to generate each coin in their respective classes
// This code should not be in a random view page file
if (coin is Monero || coin is Wownero) {
// currently a special case due to the
// xmr/wow libraries handling their
// own mnemonic generation
wordCount = ref.read(pNewWalletOptions)?.mnemonicWordsCount ??
info.coin.defaultSeedPhraseLength;
} else if (wordCount > 0) {
if (ref
.read(
pNewWalletOptions.state,
)
.state !=
null) {
if (coin.hasMnemonicPassphraseSupport) {
mnemonicPassphrase = ref
.read(
pNewWalletOptions.state,
)
.state!
.mnemonicPassphrase;
} else {
// this may not be epiccash specific?
if (coin is Epiccash) {
mnemonicPassphrase = "";
}
}
wordCount = ref
.read(
pNewWalletOptions.state,
)
.state!
.mnemonicWordsCount;
} else {
mnemonicPassphrase = "";
}
if (wordCount < 12 || 24 < wordCount || wordCount % 3 != 0) {
throw Exception(
"Invalid word count",
);
}
final strength = (wordCount ~/ 3) * 32;
mnemonic = bip39.generateMnemonic(
strength: strength,
);
}
final wallet = await Wallet.create(
walletInfo: info,
mainDB: ref.read(mainDBProvider),
secureStorageInterface: ref.read(secureStoreProvider),
nodeService: ref.read(
nodeServiceChangeNotifierProvider,
),
prefs: ref.read(
prefsChangeNotifierProvider,
),
mnemonicPassphrase: mnemonicPassphrase,
mnemonic: mnemonic,
privateKey: privateKey,
);
if (wallet is LibMoneroWallet) {
await wallet.init(wordCount: wordCount);
} else {
await wallet.init();
}
// set checkbox back to unchecked to annoy users to agree again :P
ref
.read(
checkBoxStateProvider.state,
)
.state = false;
final fetchedMnemonic =
await (wallet as MnemonicInterface).getMnemonicAsWords();
return (wallet, fetchedMnemonic);
} catch (e, s) {
Logging.instance.log(
"$e\n$s",
level: LogLevel.Fatal,
);
rethrow;
}
}
@override @override
void initState() { void initState() {
coin = widget.coin; coin = widget.coin;
@ -454,222 +671,7 @@ class _NewWalletRecoveryPhraseWarningViewState
onPressed: ref onPressed: ref
.read(checkBoxStateProvider.state) .read(checkBoxStateProvider.state)
.state .state
? () async { ? _initNewWallet
try {
unawaited(
showDialog<dynamic>(
context: context,
barrierDismissible: false,
useSafeArea: true,
builder: (ctx) {
return const Center(
child: LoadingIndicator(
width: 50,
height: 50,
),
);
},
),
);
String? otherDataJsonString;
if (widget.coin is Tezos) {
otherDataJsonString = jsonEncode({
WalletInfoKeys
.tezosDerivationPath:
Tezos.standardDerivationPath
.value,
});
// }//todo: probably not needed (broken anyways)
// else if (widget.coin is Epiccash) {
// final int secondsSinceEpoch =
// DateTime.now().millisecondsSinceEpoch ~/ 1000;
// const int epicCashFirstBlock = 1565370278;
// const double overestimateSecondsPerBlock = 61;
// int chosenSeconds = secondsSinceEpoch - epicCashFirstBlock;
// int approximateHeight = chosenSeconds ~/ overestimateSecondsPerBlock;
// /
// // debugPrint(
// // "approximate height: $approximateHeight chosen_seconds: $chosenSeconds");
// height = approximateHeight;
// if (height < 0) {
// height = 0;
// }
//
// otherDataJsonString = jsonEncode(
// {
// WalletInfoKeys.epiccashData: jsonEncode(
// ExtraEpiccashWalletInfo(
// receivingIndex: 0,
// changeIndex: 0,
// slatesToAddresses: {},
// slatesToCommits: {},
// lastScannedBlock: epicCashFirstBlock,
// restoreHeight: height,
// creationHeight: height,
// ).toMap(),
// ),
// },
// );
} else if (widget.coin is Firo) {
otherDataJsonString = jsonEncode(
{
WalletInfoKeys
.lelantusCoinIsarRescanRequired:
false,
},
);
}
final info = WalletInfo.createNew(
coin: widget.coin,
name: widget.walletName,
otherDataJsonString:
otherDataJsonString,
);
var node = ref
.read(
nodeServiceChangeNotifierProvider,
)
.getPrimaryNodeFor(
currency: coin,
);
if (node == null) {
node = coin.defaultNode;
await ref
.read(
nodeServiceChangeNotifierProvider,
)
.setPrimaryNodeFor(
coin: coin,
node: node,
);
}
final txTracker =
TransactionNotificationTracker(
walletId: info.walletId,
);
int? wordCount;
String? mnemonicPassphrase;
String? mnemonic;
String? privateKey;
wordCount = info
.coin.defaultSeedPhraseLength;
// TODO: Refactor these to generate each coin in their respective classes
// This code should not be in a random view page file
if (coin is Monero ||
coin is Wownero) {
// currently a special case due to the
// xmr/wow libraries handling their
// own mnemonic generation
} else if (wordCount > 0) {
if (ref
.read(
pNewWalletOptions.state,
)
.state !=
null) {
if (coin
.hasMnemonicPassphraseSupport) {
mnemonicPassphrase = ref
.read(
pNewWalletOptions.state,
)
.state!
.mnemonicPassphrase;
} else {
// this may not be epiccash specific?
if (coin is Epiccash) {
mnemonicPassphrase = "";
}
}
wordCount = ref
.read(
pNewWalletOptions.state,
)
.state!
.mnemonicWordsCount;
} else {
mnemonicPassphrase = "";
}
if (wordCount < 12 ||
24 < wordCount ||
wordCount % 3 != 0) {
throw Exception(
"Invalid word count",
);
}
final strength =
(wordCount ~/ 3) * 32;
mnemonic = bip39.generateMnemonic(
strength: strength,
);
}
final wallet = await Wallet.create(
walletInfo: info,
mainDB: ref.read(mainDBProvider),
secureStorageInterface:
ref.read(secureStoreProvider),
nodeService: ref.read(
nodeServiceChangeNotifierProvider,
),
prefs: ref.read(
prefsChangeNotifierProvider,
),
mnemonicPassphrase:
mnemonicPassphrase,
mnemonic: mnemonic,
privateKey: privateKey,
);
await wallet.init();
// pop progress dialog
if (context.mounted) {
Navigator.pop(context);
}
// set checkbox back to unchecked to annoy users to agree again :P
ref
.read(
checkBoxStateProvider.state,
)
.state = false;
if (context.mounted) {
final nav = Navigator.of(context);
unawaited(
nav.pushNamed(
NewWalletRecoveryPhraseView
.routeName,
arguments: Tuple2(
wallet,
await (wallet
as MnemonicInterface)
.getMnemonicAsWords(),
),
),
);
}
} catch (e, s) {
Logging.instance.log(
"$e\n$s",
level: LogLevel.Fatal,
);
// TODO: handle gracefully
// any network/socket exception here will break new wallet creation
rethrow;
}
}
: null, : null,
style: ref style: ref
.read(checkBoxStateProvider.state) .read(checkBoxStateProvider.state)

View file

@ -458,7 +458,7 @@ abstract class SWB {
mnemonic: mnemonic, mnemonic: mnemonic,
mnemonicPassphrase: mnemonicPassphrase, mnemonicPassphrase: mnemonicPassphrase,
); );
Wallet? wallet;
try { try {
String? serializedKeys; String? serializedKeys;
String? multisigConfig; String? multisigConfig;
@ -491,7 +491,7 @@ abstract class SWB {
}); });
} }
final wallet = await Wallet.create( wallet = await Wallet.create(
walletInfo: info, walletInfo: info,
mainDB: MainDB.instance, mainDB: MainDB.instance,
secureStorageInterface: secureStorageInterface, secureStorageInterface: secureStorageInterface,
@ -614,6 +614,8 @@ abstract class SWB {
mnemonicPassphrase: mnemonicPassphrase, mnemonicPassphrase: mnemonicPassphrase,
); );
return false; return false;
} finally {
await wallet?.exit();
} }
return true; return true;
} }

View file

@ -316,9 +316,10 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
if (wallet is MnemonicInterface) { if (wallet is MnemonicInterface) {
if (wallet if (wallet
is ViewOnlyOptionInterface && is ViewOnlyOptionInterface &&
!(wallet (wallet as ViewOnlyOptionInterface)
as ViewOnlyOptionInterface)
.isViewOnly) { .isViewOnly) {
// TODO: is something needed here?
} else {
mnemonic = await wallet mnemonic = await wallet
.getMnemonicAsWords(); .getMnemonicAsWords();
} }

View file

@ -85,12 +85,26 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
await mainDB.isar.frostWalletInfo.put(frostWalletInfo); await mainDB.isar.frostWalletInfo.put(frostWalletInfo);
}); });
final address = await _generateAddress( Address? address;
int index = kFrostSecureStartingIndex;
while (address == null) {
try {
address = await _generateAddress(
change: 0, change: 0,
index: kFrostSecureStartingIndex, index: index,
serializedKeys: serializedKeys, serializedKeys: serializedKeys,
secure: true, secure: true,
); );
} on FrostdartException catch (e) {
if (e.errorCode == 72) {
// rust doesn't like the addressDerivationData
index++;
continue;
} else {
rethrow;
}
}
}
await mainDB.putAddresses([address]); await mainDB.putAddresses([address]);
} catch (e, s) { } catch (e, s) {

View file

@ -701,7 +701,7 @@ class EpiccashWallet extends Bip39Wallet {
isar: mainDB.isar, isar: mainDB.isar,
); );
unawaited(_startScans()); unawaited(refresh(doScan: true));
} else { } else {
await updateNode(); await updateNode();
final String password = generatePassword(); final String password = generatePassword();
@ -759,9 +759,8 @@ class EpiccashWallet extends Bip39Wallet {
epicData.receivingIndex, epicData.receivingIndex,
); );
} }
unawaited(refresh(doScan: false));
}); });
unawaited(refresh());
} catch (e, s) { } catch (e, s) {
Logging.instance.log( Logging.instance.log(
"Exception rethrown from electrumx_mixin recover(): $e\n$s", "Exception rethrown from electrumx_mixin recover(): $e\n$s",
@ -773,7 +772,7 @@ class EpiccashWallet extends Bip39Wallet {
} }
@override @override
Future<void> refresh() async { Future<void> refresh({bool doScan = true}) async {
// Awaiting this lock could be dangerous. // Awaiting this lock could be dangerous.
// Since refresh is periodic (generally) // Since refresh is periodic (generally)
if (refreshMutex.isLocked) { if (refreshMutex.isLocked) {
@ -803,9 +802,11 @@ class EpiccashWallet extends Bip39Wallet {
final int curAdd = await _getCurrentIndex(); final int curAdd = await _getCurrentIndex();
await _generateAndStoreReceivingAddressForIndex(curAdd); await _generateAndStoreReceivingAddressForIndex(curAdd);
if (doScan) {
await _startScans(); await _startScans();
unawaited(_startSync()); unawaited(_startSync());
}
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId)); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId));
await updateChainHeight(); await updateChainHeight();
@ -1157,6 +1158,7 @@ class EpiccashWallet extends Bip39Wallet {
@override @override
Future<void> exit() async { Future<void> exit() async {
epiccash.LibEpiccash.stopEpicboxListener();
timer?.cancel(); timer?.cancel();
timer = null; timer = null;
await super.exit(); await super.exit();

View file

@ -75,13 +75,28 @@ class MoneroWallet extends LibMoneroWallet {
Future<lib_monero.Wallet> getCreatedWallet({ Future<lib_monero.Wallet> getCreatedWallet({
required String path, required String path,
required String password, required String password,
}) async => required int wordCount,
await lib_monero.MoneroWallet.create( }) async {
final lib_monero.MoneroSeedType type;
switch (wordCount) {
case 16:
type = lib_monero.MoneroSeedType.sixteen;
break;
case 25:
type = lib_monero.MoneroSeedType.twentyFive;
break;
default:
throw Exception("Invalid mnemonic word count: $wordCount");
}
return await lib_monero.MoneroWallet.create(
path: path, path: path,
password: password, password: password,
seedType: lib_monero.MoneroSeedType seedType: type,
.sixteen, // TODO: check we want to actually use 16 here
); );
}
@override @override
Future<lib_monero.Wallet> getRestoredWallet({ Future<lib_monero.Wallet> getRestoredWallet({

View file

@ -111,14 +111,33 @@ class WowneroWallet extends LibMoneroWallet {
Future<lib_monero.Wallet> getCreatedWallet({ Future<lib_monero.Wallet> getCreatedWallet({
required String path, required String path,
required String password, required String password,
}) async => required int wordCount,
await lib_monero.WowneroWallet.create( }) async {
final lib_monero.WowneroSeedType type;
switch (wordCount) {
case 14:
type = lib_monero.WowneroSeedType.fourteen;
break;
case 16:
type = lib_monero.WowneroSeedType.sixteen;
break;
case 25:
type = lib_monero.WowneroSeedType.twentyFive;
break;
default:
throw Exception("Invalid mnemonic word count: $wordCount");
}
return await lib_monero.WowneroWallet.create(
path: path, path: path,
password: password, password: password,
seedType: lib_monero.WowneroSeedType seedType: type,
.fourteen, // TODO: check we want to actually use 14 here
overrideDeprecated14WordSeedException: true, overrideDeprecated14WordSeedException: true,
); );
}
@override @override
Future<lib_monero.Wallet> getRestoredWallet({ Future<lib_monero.Wallet> getRestoredWallet({

View file

@ -142,6 +142,7 @@ abstract class LibMoneroWallet<T extends CryptonoteCurrency>
Future<lib_monero.Wallet> getCreatedWallet({ Future<lib_monero.Wallet> getCreatedWallet({
required String path, required String path,
required String password, required String password,
required int wordCount,
}); });
Future<lib_monero.Wallet> getRestoredWallet({ Future<lib_monero.Wallet> getRestoredWallet({
@ -313,19 +314,26 @@ abstract class LibMoneroWallet<T extends CryptonoteCurrency>
} }
@override @override
Future<void> init({bool? isRestore}) async { Future<void> init({bool? isRestore, int? wordCount}) async {
final path = await pathForWallet( final path = await pathForWallet(
name: walletId, name: walletId,
type: compatType, type: compatType,
); );
if (!(walletExists(path)) && isRestore != true) { if (!(walletExists(path)) && isRestore != true) {
if (wordCount == null) {
throw Exception("Missing word count for new xmr/wow wallet!");
}
try { try {
final password = generatePassword(); final password = generatePassword();
await secureStorageInterface.write( await secureStorageInterface.write(
key: lib_monero_compat.libMoneroWalletPasswordKey(walletId), key: lib_monero_compat.libMoneroWalletPasswordKey(walletId),
value: password, value: password,
); );
final wallet = await getCreatedWallet(path: path, password: password); final wallet = await getCreatedWallet(
path: path,
password: password,
wordCount: wordCount,
);
final height = wallet.getRefreshFromBlockHeight(); final height = wallet.getRefreshFromBlockHeight();

View file

@ -23,8 +23,7 @@
android:requestLegacyExternalStorage="true" android:requestLegacyExternalStorage="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:allowBackup="false" android:allowBackup="false"
android:fullBackupContent="false" android:fullBackupContent="false">
android:extractNativeLibs="true">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:screenOrientation="portrait" android:screenOrientation="portrait"