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 '../../../utilities/assets.dart';
import '../../../utilities/logger.dart';
import '../../../utilities/show_loading.dart';
import '../../../utilities/text_styles.dart';
import '../../../utilities/util.dart';
import '../../../wallets/crypto_currency/crypto_currency.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_mixin_interfaces/mnemonic_interface.dart';
import '../../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../../widgets/desktop/desktop_app_bar.dart';
import '../../../widgets/desktop/desktop_scaffold.dart';
import '../../../widgets/loading_indicator.dart';
import '../../../widgets/rounded_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_recovery_phrase_view/new_wallet_recovery_phrase_view.dart';
import 'recovery_phrase_explanation_dialog.dart';
@ -65,6 +67,221 @@ class _NewWalletRecoveryPhraseWarningViewState
late final String walletName;
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
void initState() {
coin = widget.coin;
@ -454,222 +671,7 @@ class _NewWalletRecoveryPhraseWarningViewState
onPressed: ref
.read(checkBoxStateProvider.state)
.state
? () async {
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;
}
}
? _initNewWallet
: null,
style: ref
.read(checkBoxStateProvider.state)

View file

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

View file

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

View file

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

View file

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

View file

@ -75,13 +75,28 @@ class MoneroWallet extends LibMoneroWallet {
Future<lib_monero.Wallet> getCreatedWallet({
required String path,
required String password,
}) async =>
await lib_monero.MoneroWallet.create(
required int wordCount,
}) 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,
password: password,
seedType: lib_monero.MoneroSeedType
.sixteen, // TODO: check we want to actually use 16 here
seedType: type,
);
}
@override
Future<lib_monero.Wallet> getRestoredWallet({

View file

@ -111,14 +111,33 @@ class WowneroWallet extends LibMoneroWallet {
Future<lib_monero.Wallet> getCreatedWallet({
required String path,
required String password,
}) async =>
await lib_monero.WowneroWallet.create(
required int wordCount,
}) 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,
password: password,
seedType: lib_monero.WowneroSeedType
.fourteen, // TODO: check we want to actually use 14 here
seedType: type,
overrideDeprecated14WordSeedException: true,
);
}
@override
Future<lib_monero.Wallet> getRestoredWallet({

View file

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

View file

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