mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-06-29 04:49:51 +00:00
Cw 462 monero polyseed restore support (#1109)
* CW-462 Mark Places to integrate Polyseed * CW-462 Add Restore from Polyseed * CW-462 Add Restore from Polyseed * CW-462 Add new Monero date-height pairs * CW-462 Little Cleanup * CW-462 Ups I missed that Debug line :/ * CW-462 Fix Polyseed not showing in Wallet-Seed/Keys Page * CW-462 Prepare for Wallet creation * CW-462 Fix merge conflict * CW-462 Fix generating monero.dart * CW-462 Add Polyseed generation * CW-462 Add Polyseed Languages to SeedLanguagePicker * CW-462 Apply requested changes * CW-462 Minor bug fixes in restore screen * Update wallet_restore_from_seed_form.dart * CW-462 Minor Bugfix * CW-462 Fix Restore from QR for Polyseeds * CW-462 Fix null-check-operator exception for Polyseeds and minor inconveniences * CW-462 Fix minor inconveniences * Fix conflicts and review comments and wrap unspent issue with try and catch with reporting failure --------- Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
This commit is contained in:
parent
eeb9976d09
commit
00c97c74b8
57 changed files with 906 additions and 444 deletions
|
@ -301,10 +301,19 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: pointycastle
|
||||
sha256: db7306cf0249f838d1a24af52b5a5887c5bf7f31d8bb4e827d071dc0939ad346
|
||||
sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.6.2"
|
||||
version: "3.7.3"
|
||||
polyseed:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "."
|
||||
ref: HEAD
|
||||
resolved-ref: "504d58a5b147fccd3bc85a25f2e72fb32771ddd7"
|
||||
url: "https://github.com/cake-tech/polyseed_dart.git"
|
||||
source: git
|
||||
version: "0.0.1"
|
||||
process:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -416,5 +425,5 @@ packages:
|
|||
source: hosted
|
||||
version: "0.2.0+3"
|
||||
sdks:
|
||||
dart: ">=3.0.0 <4.0.0"
|
||||
dart: ">=3.0.6 <4.0.0"
|
||||
flutter: ">=3.7.0"
|
||||
|
|
|
@ -374,6 +374,35 @@ extern "C"
|
|||
return true;
|
||||
}
|
||||
|
||||
bool restore_wallet_from_spend_key(char *path, char *password, char *seed, char *language, char *spendKey, int32_t networkType, uint64_t restoreHeight, char *error)
|
||||
{
|
||||
Monero::NetworkType _networkType = static_cast<Monero::NetworkType>(networkType);
|
||||
Monero::Wallet *wallet = Monero::WalletManagerFactory::getWalletManager()->createDeterministicWalletFromSpendKey(
|
||||
std::string(path),
|
||||
std::string(password),
|
||||
std::string(language),
|
||||
_networkType,
|
||||
(uint64_t)restoreHeight,
|
||||
std::string(spendKey));
|
||||
|
||||
int status;
|
||||
std::string errorString;
|
||||
|
||||
wallet->statusWithErrorString(status, errorString);
|
||||
|
||||
if (status != Monero::Wallet::Status_Ok || !errorString.empty())
|
||||
{
|
||||
error = strdup(errorString.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cache Raw to support Polyseed
|
||||
wallet->setCacheAttribute("cakewallet.seed", std::string(seed));
|
||||
|
||||
change_current_wallet(wallet);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool load_wallet(char *path, char *password, int32_t nettype)
|
||||
{
|
||||
nice(19);
|
||||
|
@ -438,6 +467,11 @@ extern "C"
|
|||
|
||||
const char *seed()
|
||||
{
|
||||
std::string _rawSeed = get_current_wallet()->getCacheAttribute("cakewallet.seed");
|
||||
if (!_rawSeed.empty())
|
||||
{
|
||||
return strdup(_rawSeed.c_str());
|
||||
}
|
||||
return strdup(get_current_wallet()->seed().c_str());
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,9 @@ typedef restore_wallet_from_seed = Int8 Function(
|
|||
typedef restore_wallet_from_keys = Int8 Function(Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>,
|
||||
Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>, Int32, Int64, Pointer<Utf8>);
|
||||
|
||||
typedef restore_wallet_from_spend_key = Int8 Function(Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>,
|
||||
Pointer<Utf8>, Pointer<Utf8>, Int32, Int64, Pointer<Utf8>);
|
||||
|
||||
typedef is_wallet_exist = Int8 Function(Pointer<Utf8>);
|
||||
|
||||
typedef load_wallet = Int8 Function(Pointer<Utf8>, Pointer<Utf8>, Int8);
|
||||
|
|
|
@ -14,6 +14,9 @@ typedef RestoreWalletFromSeed = int Function(
|
|||
typedef RestoreWalletFromKeys = int Function(Pointer<Utf8>, Pointer<Utf8>,
|
||||
Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>, int, int, Pointer<Utf8>);
|
||||
|
||||
typedef RestoreWalletFromSpendKey = int Function(Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>,
|
||||
Pointer<Utf8>, Pointer<Utf8>, int, int, Pointer<Utf8>);
|
||||
|
||||
typedef IsWalletExist = int Function(Pointer<Utf8>);
|
||||
|
||||
typedef LoadWallet = int Function(Pointer<Utf8>, Pointer<Utf8>, int);
|
||||
|
|
|
@ -5,7 +5,6 @@ import 'package:cw_monero/api/convert_utf8_to_string.dart';
|
|||
import 'package:cw_monero/api/signatures.dart';
|
||||
import 'package:cw_monero/api/types.dart';
|
||||
import 'package:cw_monero/api/monero_api.dart';
|
||||
import 'package:cw_monero/api/wallet.dart';
|
||||
import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart';
|
||||
import 'package:cw_monero/api/exceptions/wallet_creation_exception.dart';
|
||||
import 'package:cw_monero/api/exceptions/wallet_restore_from_keys_exception.dart';
|
||||
|
@ -25,6 +24,11 @@ final restoreWalletFromKeysNative = moneroApi
|
|||
'restore_wallet_from_keys')
|
||||
.asFunction<RestoreWalletFromKeys>();
|
||||
|
||||
final restoreWalletFromSpendKeyNative = moneroApi
|
||||
.lookup<NativeFunction<restore_wallet_from_spend_key>>(
|
||||
'restore_wallet_from_spend_key')
|
||||
.asFunction<RestoreWalletFromSpendKey>();
|
||||
|
||||
final isWalletExistNative = moneroApi
|
||||
.lookup<NativeFunction<is_wallet_exist>>('is_wallet_exist')
|
||||
.asFunction<IsWalletExist>();
|
||||
|
@ -141,6 +145,42 @@ void restoreWalletFromKeysSync(
|
|||
}
|
||||
}
|
||||
|
||||
void restoreWalletFromSpendKeySync(
|
||||
{required String path,
|
||||
required String password,
|
||||
required String seed,
|
||||
required String language,
|
||||
required String spendKey,
|
||||
int nettype = 0,
|
||||
int restoreHeight = 0}) {
|
||||
final pathPointer = path.toNativeUtf8();
|
||||
final passwordPointer = password.toNativeUtf8();
|
||||
final seedPointer = seed.toNativeUtf8();
|
||||
final languagePointer = language.toNativeUtf8();
|
||||
final spendKeyPointer = spendKey.toNativeUtf8();
|
||||
final errorMessagePointer = ''.toNativeUtf8();
|
||||
final isWalletRestored = restoreWalletFromSpendKeyNative(
|
||||
pathPointer,
|
||||
passwordPointer,
|
||||
seedPointer,
|
||||
languagePointer,
|
||||
spendKeyPointer,
|
||||
nettype,
|
||||
restoreHeight,
|
||||
errorMessagePointer) !=
|
||||
0;
|
||||
|
||||
calloc.free(pathPointer);
|
||||
calloc.free(passwordPointer);
|
||||
calloc.free(languagePointer);
|
||||
calloc.free(spendKeyPointer);
|
||||
|
||||
if (!isWalletRestored) {
|
||||
throw WalletRestoreFromKeysException(
|
||||
message: convertUTF8ToString(pointer: errorMessagePointer));
|
||||
}
|
||||
}
|
||||
|
||||
void loadWallet({
|
||||
required String path,
|
||||
required String password,
|
||||
|
@ -194,6 +234,23 @@ void _restoreFromKeys(Map<String, dynamic> args) {
|
|||
spendKey: spendKey);
|
||||
}
|
||||
|
||||
void _restoreFromSpendKey(Map<String, dynamic> args) {
|
||||
final path = args['path'] as String;
|
||||
final password = args['password'] as String;
|
||||
final seed = args['seed'] as String;
|
||||
final language = args['language'] as String;
|
||||
final spendKey = args['spendKey'] as String;
|
||||
final restoreHeight = args['restoreHeight'] as int;
|
||||
|
||||
restoreWalletFromSpendKeySync(
|
||||
path: path,
|
||||
password: password,
|
||||
seed: seed,
|
||||
language: language,
|
||||
restoreHeight: restoreHeight,
|
||||
spendKey: spendKey);
|
||||
}
|
||||
|
||||
Future<void> _openWallet(Map<String, String> args) async =>
|
||||
loadWallet(path: args['path'] as String, password: args['password'] as String);
|
||||
|
||||
|
@ -251,4 +308,22 @@ Future<void> restoreFromKeys(
|
|||
'restoreHeight': restoreHeight
|
||||
});
|
||||
|
||||
Future<void> restoreFromSpendKey(
|
||||
{required String path,
|
||||
required String password,
|
||||
required String seed,
|
||||
required String language,
|
||||
required String spendKey,
|
||||
int nettype = 0,
|
||||
int restoreHeight = 0}) async =>
|
||||
compute<Map<String, Object>, void>(_restoreFromSpendKey, {
|
||||
'path': path,
|
||||
'password': password,
|
||||
'seed': seed,
|
||||
'language': language,
|
||||
'spendKey': spendKey,
|
||||
'nettype': nettype,
|
||||
'restoreHeight': restoreHeight
|
||||
});
|
||||
|
||||
Future<bool> isWalletExist({required String path}) => compute(_isWalletExist, path);
|
||||
|
|
|
@ -29,6 +29,7 @@ import 'package:cw_monero/monero_transaction_info.dart';
|
|||
import 'package:cw_monero/monero_unspent.dart';
|
||||
import 'package:cw_monero/monero_wallet_addresses.dart';
|
||||
import 'package:cw_monero/pending_monero_transaction.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
|
@ -78,6 +79,8 @@ abstract class MoneroWalletBase
|
|||
|
||||
Box<UnspentCoinsInfo> unspentCoinsInfo;
|
||||
|
||||
void Function(FlutterErrorDetails)? _onError;
|
||||
|
||||
@override
|
||||
late MoneroWalletAddresses walletAddresses;
|
||||
|
||||
|
@ -388,46 +391,59 @@ abstract class MoneroWalletBase
|
|||
}
|
||||
|
||||
Future<void> updateUnspent() async {
|
||||
refreshCoins(walletAddresses.account!.id);
|
||||
try {
|
||||
refreshCoins(walletAddresses.account!.id);
|
||||
|
||||
unspentCoins.clear();
|
||||
unspentCoins.clear();
|
||||
|
||||
final coinCount = countOfCoins();
|
||||
for (var i = 0; i < coinCount; i++) {
|
||||
final coin = getCoin(i);
|
||||
if (coin.spent == 0) {
|
||||
final unspent = MoneroUnspent.fromCoinsInfoRow(coin);
|
||||
unspent.isChange = transaction_history.getTransaction(unspent.hash).direction == 1;
|
||||
unspentCoins.add(unspent);
|
||||
}
|
||||
}
|
||||
|
||||
if (unspentCoinsInfo.isEmpty) {
|
||||
unspentCoins.forEach((coin) => _addCoinInfo(coin));
|
||||
return;
|
||||
}
|
||||
|
||||
if (unspentCoins.isNotEmpty) {
|
||||
unspentCoins.forEach((coin) {
|
||||
final coinInfoList = unspentCoinsInfo.values.where((element) =>
|
||||
element.walletId.contains(id) &&
|
||||
element.accountIndex == walletAddresses.account!.id &&
|
||||
element.keyImage!.contains(coin.keyImage!));
|
||||
|
||||
if (coinInfoList.isNotEmpty) {
|
||||
final coinInfo = coinInfoList.first;
|
||||
|
||||
coin.isFrozen = coinInfo.isFrozen;
|
||||
coin.isSending = coinInfo.isSending;
|
||||
coin.note = coinInfo.note;
|
||||
} else {
|
||||
_addCoinInfo(coin);
|
||||
final coinCount = countOfCoins();
|
||||
for (var i = 0; i < coinCount; i++) {
|
||||
final coin = getCoin(i);
|
||||
if (coin.spent == 0) {
|
||||
final unspent = MoneroUnspent.fromCoinsInfoRow(coin);
|
||||
if (unspent.hash.isNotEmpty) {
|
||||
unspent.isChange = transaction_history
|
||||
.getTransaction(unspent.hash)
|
||||
.direction == 1;
|
||||
}
|
||||
unspentCoins.add(unspent);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await _refreshUnspentCoinsInfo();
|
||||
_askForUpdateBalance();
|
||||
if (unspentCoinsInfo.isEmpty) {
|
||||
unspentCoins.forEach((coin) => _addCoinInfo(coin));
|
||||
return;
|
||||
}
|
||||
|
||||
if (unspentCoins.isNotEmpty) {
|
||||
unspentCoins.forEach((coin) {
|
||||
final coinInfoList = unspentCoinsInfo.values.where((element) =>
|
||||
element.walletId.contains(id) &&
|
||||
element.accountIndex == walletAddresses.account!.id &&
|
||||
element.keyImage!.contains(coin.keyImage!));
|
||||
|
||||
if (coinInfoList.isNotEmpty) {
|
||||
final coinInfo = coinInfoList.first;
|
||||
|
||||
coin.isFrozen = coinInfo.isFrozen;
|
||||
coin.isSending = coinInfo.isSending;
|
||||
coin.note = coinInfo.note;
|
||||
} else {
|
||||
_addCoinInfo(coin);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await _refreshUnspentCoinsInfo();
|
||||
_askForUpdateBalance();
|
||||
} catch (e, s) {
|
||||
print(e.toString());
|
||||
_onError?.call(FlutterErrorDetails(
|
||||
exception: e,
|
||||
stack: s,
|
||||
library: this.runtimeType.toString(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _addCoinInfo(MoneroUnspent coin) async {
|
||||
|
@ -632,4 +648,7 @@ abstract class MoneroWalletBase
|
|||
walletAddresses.updateSubaddressList(accountIndex: account?.id ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void setExceptionHandler(void Function(FlutterErrorDetails) onError) => _onError = onError;
|
||||
}
|
||||
|
|
|
@ -7,16 +7,20 @@ import 'package:cw_core/wallet_credentials.dart';
|
|||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_service.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_core/get_height_by_date.dart';
|
||||
import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart';
|
||||
import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager;
|
||||
import 'package:cw_monero/monero_wallet.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:polyseed/polyseed.dart';
|
||||
import 'package:polyseed/src/utils/key_utils.dart';
|
||||
|
||||
class MoneroNewWalletCredentials extends WalletCredentials {
|
||||
MoneroNewWalletCredentials({required String name, required this.language, String? password})
|
||||
MoneroNewWalletCredentials({required String name, required this.language, required this.isPolyseed, String? password})
|
||||
: super(name: name, password: password);
|
||||
|
||||
final String language;
|
||||
final bool isPolyseed;
|
||||
}
|
||||
|
||||
class MoneroRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||
|
@ -68,10 +72,17 @@ class MoneroWalletService extends WalletService<
|
|||
Future<MoneroWallet> create(MoneroNewWalletCredentials credentials) async {
|
||||
try {
|
||||
final path = await pathForWallet(name: credentials.name, type: getType());
|
||||
|
||||
if (credentials.isPolyseed) {
|
||||
final polyseed = Polyseed.create();
|
||||
final lang = PolyseedLang.getByEnglishName(credentials.language);
|
||||
|
||||
return _restoreFromPolyseed(
|
||||
path, credentials.password!, polyseed, credentials.walletInfo!, lang);
|
||||
}
|
||||
|
||||
await monero_wallet_manager.createWallet(
|
||||
path: path,
|
||||
password: credentials.password!,
|
||||
language: credentials.language);
|
||||
path: path, password: credentials.password!, language: credentials.language);
|
||||
final wallet = MoneroWallet(
|
||||
walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
await wallet.init();
|
||||
|
@ -215,6 +226,12 @@ class MoneroWalletService extends WalletService<
|
|||
@override
|
||||
Future<MoneroWallet> restoreFromSeed(
|
||||
MoneroRestoreWalletFromSeedCredentials credentials) async {
|
||||
|
||||
// Restore from Polyseed
|
||||
if (Polyseed.isValidSeed(credentials.mnemonic)) {
|
||||
return restoreFromPolyseed(credentials);
|
||||
}
|
||||
|
||||
try {
|
||||
final path = await pathForWallet(name: credentials.name, type: getType());
|
||||
await monero_wallet_manager.restoreFromSeed(
|
||||
|
@ -234,6 +251,42 @@ class MoneroWalletService extends WalletService<
|
|||
}
|
||||
}
|
||||
|
||||
Future<MoneroWallet> restoreFromPolyseed(MoneroRestoreWalletFromSeedCredentials credentials) async {
|
||||
try {
|
||||
final path = await pathForWallet(name: credentials.name, type: getType());
|
||||
final polyseedCoin = PolyseedCoin.POLYSEED_MONERO;
|
||||
final lang = PolyseedLang.getByPhrase(credentials.mnemonic);
|
||||
final polyseed = Polyseed.decode(credentials.mnemonic, lang, polyseedCoin);
|
||||
|
||||
return _restoreFromPolyseed(path, credentials.password!, polyseed, credentials.walletInfo!, lang);
|
||||
} catch (e) {
|
||||
// TODO: Implement Exception for wallet list service.
|
||||
print('MoneroWalletsManager Error: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<MoneroWallet> _restoreFromPolyseed(String path, String password, Polyseed polyseed,
|
||||
WalletInfo walletInfo, PolyseedLang lang,
|
||||
{PolyseedCoin coin = PolyseedCoin.POLYSEED_MONERO}) async {
|
||||
final height = getMoneroHeigthByDate(
|
||||
date: DateTime.fromMillisecondsSinceEpoch(polyseed.birthday * 1000));
|
||||
final spendKey = keyToHexString(polyseed.generateKey(coin, 32));
|
||||
|
||||
await monero_wallet_manager.restoreFromSpendKey(
|
||||
path: path,
|
||||
password: password,
|
||||
seed: polyseed.encode(lang, coin),
|
||||
language: lang.nameEnglish,
|
||||
restoreHeight: height,
|
||||
spendKey: spendKey);
|
||||
final wallet = MoneroWallet(
|
||||
walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
await wallet.init();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
Future<void> repairOldAndroidWallet(String name) async {
|
||||
try {
|
||||
if (!Platform.isAndroid) {
|
||||
|
|
|
@ -478,10 +478,19 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: pointycastle
|
||||
sha256: db7306cf0249f838d1a24af52b5a5887c5bf7f31d8bb4e827d071dc0939ad346
|
||||
sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.6.2"
|
||||
version: "3.7.3"
|
||||
polyseed:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: HEAD
|
||||
resolved-ref: "504d58a5b147fccd3bc85a25f2e72fb32771ddd7"
|
||||
url: "https://github.com/cake-tech/polyseed_dart.git"
|
||||
source: git
|
||||
version: "0.0.1"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -689,5 +698,5 @@ packages:
|
|||
source: hosted
|
||||
version: "3.1.1"
|
||||
sdks:
|
||||
dart: ">=3.0.0 <4.0.0"
|
||||
dart: ">=3.0.6 <4.0.0"
|
||||
flutter: ">=3.7.0"
|
||||
|
|
|
@ -19,6 +19,9 @@ dependencies:
|
|||
flutter_mobx: ^2.0.6+1
|
||||
intl: ^0.18.0
|
||||
encrypt: ^5.0.1
|
||||
polyseed:
|
||||
git:
|
||||
url: https://github.com/cake-tech/polyseed_dart.git
|
||||
cw_core:
|
||||
path: ../cw_core
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue