fix new xmr/wow wallet creation when specifying mnemonic length

This commit is contained in:
julian 2024-12-03 16:19:09 -06:00
parent 86e3bf0349
commit 64dd830e58
4 changed files with 278 additions and 234 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

@ -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();