mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-06-28 12:29:51 +00:00
CW-723-Add-Monero-support-to-the-Shared-Seed-feature-in-Cake (#2131)
* feat: add exodus style bip39 to monero legacy seed * feat: restore monero wallet from bip39 and add test * bug: fix wrong naming in CI * feat: add monero bip39 UI flow * fix: monero.dart generation * fix: skip monero_wallet_service tests till CI is fixed * ci: copy monero_libwallet2_api_c.so to /usr/lib for testing ci: reduce timeout for cw_monero tests * fix: monero wallet creation credentials default to bip39 if mnemonic are set * fix: do not skip monero wallets services test * fix: Include non bip39 monero wallets on Wallet Group * fix: null pointer stemming from missing language selector if seed is selected * fix: Fixes to Bip39 Creation and restore - Do not restore from 0 for fresh bip39 wallet - disallow restoring bip39 wallet without date or height * fix: Fixes to Bip39 restore - Refresh height is now getting set correctly - Add new create monero wallet tests - Add seed-language English for Bip39 Monero wallets - Fix seed-type naming * feat (cw_monero): Store monero wallet after bip39 creation * feat (cw_monero): remove prints from monero_wallet_service_test.dart * fix: exception during seed language autodetect * feat (cw_monero): Add support for passphrases on bip39 seeds * feat (cw_monero): Add support for passphrases on bip39 seeds * fix: seed language selection for recovering bip39 wallets * style: improve readability of isLegacySeedOnly in wallet_keys_view_model.dart * feat: hide monero seed type selector from advanced settings when creating a child wallet * fix(cw_monero): use named arguments for bip39_seed tests --------- Co-authored-by: cyan <cyjan@mrcyjanek.net>
This commit is contained in:
parent
494207290e
commit
f58a5fb8fd
51 changed files with 702 additions and 283 deletions
3
cw_monero/.gitignore
vendored
3
cw_monero/.gitignore
vendored
|
@ -11,4 +11,5 @@ android/.externalNativeBuild/
|
|||
android/.cxx/
|
||||
|
||||
macos/cw_monero.podspec
|
||||
macos/External/
|
||||
macos/External/
|
||||
*monero_libwallet2_api_c.*
|
|
@ -62,6 +62,11 @@ String getSeed() {
|
|||
}
|
||||
return cakepolyseed;
|
||||
}
|
||||
|
||||
final bip39 = monero.Wallet_getCacheAttribute(wptr!, key: "cakewallet.seed.bip39");
|
||||
|
||||
if(bip39.isNotEmpty) return bip39;
|
||||
|
||||
final legacy = getSeedLegacy(null);
|
||||
return legacy;
|
||||
}
|
||||
|
|
59
cw_monero/lib/bip39_seed.dart
Normal file
59
cw_monero/lib/bip39_seed.dart
Normal file
|
@ -0,0 +1,59 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:bip32/bip32.dart' as bip32;
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:polyseed/polyseed.dart';
|
||||
|
||||
bool isBip39Seed(String mnemonic) => bip39.validateMnemonic(mnemonic);
|
||||
|
||||
String getBip39Seed() => bip39.generateMnemonic();
|
||||
|
||||
String getLegacySeedFromBip39(String mnemonic,
|
||||
{int accountIndex = 0, String passphrase = ""}) {
|
||||
final seed = bip39.mnemonicToSeed(mnemonic, passphrase: passphrase);
|
||||
|
||||
final bip32KeyPair =
|
||||
bip32.BIP32.fromSeed(seed).derivePath("m/44'/128'/$accountIndex'/0/0");
|
||||
|
||||
final spendKey = _reduceECKey(bip32KeyPair.privateKey!);
|
||||
|
||||
return LegacySeedLang.getByEnglishName("English")
|
||||
.encodePhrase(spendKey.toHexString());
|
||||
}
|
||||
|
||||
const _ed25519CurveOrder =
|
||||
"1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED";
|
||||
|
||||
Uint8List _reduceECKey(Uint8List buffer) {
|
||||
final curveOrder = BigInt.parse(_ed25519CurveOrder, radix: 16);
|
||||
final bigNumber = _readBytes(buffer);
|
||||
|
||||
var result = bigNumber % curveOrder;
|
||||
|
||||
final resultBuffer = Uint8List(32);
|
||||
for (var i = 0; i < 32; i++) {
|
||||
resultBuffer[i] = (result & BigInt.from(0xff)).toInt();
|
||||
result = result >> 8;
|
||||
}
|
||||
|
||||
return resultBuffer;
|
||||
}
|
||||
|
||||
/// Read BigInt from a little-endian Uint8List
|
||||
/// From https://github.com/dart-lang/sdk/issues/32803#issuecomment-387405784
|
||||
BigInt _readBytes(Uint8List bytes) {
|
||||
BigInt read(int start, int end) {
|
||||
if (end - start <= 4) {
|
||||
var result = 0;
|
||||
for (int i = end - 1; i >= start; i--) {
|
||||
result = result * 256 + bytes[i];
|
||||
}
|
||||
return BigInt.from(result);
|
||||
}
|
||||
final mid = start + ((end - start) >> 1);
|
||||
return read(start, mid) +
|
||||
read(mid, end) * (BigInt.one << ((mid - start) * 8));
|
||||
}
|
||||
|
||||
return read(0, bytes.length);
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cw_core/get_height_by_date.dart';
|
||||
import 'package:cw_core/monero_wallet_utils.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
|
@ -14,29 +15,38 @@ import 'package:cw_core/wallet_type.dart';
|
|||
import 'package:cw_monero/api/account_list.dart';
|
||||
import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager;
|
||||
import 'package:cw_monero/api/wallet_manager.dart';
|
||||
import 'package:cw_monero/bip39_seed.dart';
|
||||
import 'package:cw_monero/ledger.dart';
|
||||
import 'package:cw_monero/monero_wallet.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
|
||||
import 'package:monero/monero.dart' as monero;
|
||||
import 'package:polyseed/polyseed.dart';
|
||||
|
||||
enum MoneroSeedType { polyseed, legacy, bip39 }
|
||||
|
||||
class MoneroNewWalletCredentials extends WalletCredentials {
|
||||
MoneroNewWalletCredentials(
|
||||
{required String name, required this.language, required this.isPolyseed, String? password, this.passphrase})
|
||||
{required String name,
|
||||
required this.language,
|
||||
required this.seedType,
|
||||
String? password,
|
||||
this.passphrase,
|
||||
this.mnemonic})
|
||||
: super(name: name, password: password);
|
||||
|
||||
final String language;
|
||||
final bool isPolyseed;
|
||||
final MoneroSeedType seedType;
|
||||
final String? passphrase;
|
||||
final String? mnemonic;
|
||||
}
|
||||
|
||||
class MoneroRestoreWalletFromHardwareCredentials extends WalletCredentials {
|
||||
MoneroRestoreWalletFromHardwareCredentials({required String name,
|
||||
required this.ledgerConnection,
|
||||
int height = 0,
|
||||
String? password})
|
||||
MoneroRestoreWalletFromHardwareCredentials(
|
||||
{required String name,
|
||||
required this.ledgerConnection,
|
||||
int height = 0,
|
||||
String? password})
|
||||
: super(name: name, password: password, height: height);
|
||||
LedgerConnection ledgerConnection;
|
||||
}
|
||||
|
@ -60,13 +70,14 @@ class MoneroWalletLoadingException implements Exception {
|
|||
}
|
||||
|
||||
class MoneroRestoreWalletFromKeysCredentials extends WalletCredentials {
|
||||
MoneroRestoreWalletFromKeysCredentials({required String name,
|
||||
required String password,
|
||||
required this.language,
|
||||
required this.address,
|
||||
required this.viewKey,
|
||||
required this.spendKey,
|
||||
int height = 0})
|
||||
MoneroRestoreWalletFromKeysCredentials(
|
||||
{required String name,
|
||||
required String password,
|
||||
required this.language,
|
||||
required this.address,
|
||||
required this.viewKey,
|
||||
required this.spendKey,
|
||||
int height = 0})
|
||||
: super(name: name, password: password, height: height);
|
||||
|
||||
final String language;
|
||||
|
@ -97,27 +108,42 @@ class MoneroWalletService extends WalletService<
|
|||
@override
|
||||
WalletType getType() => WalletType.monero;
|
||||
|
||||
@override
|
||||
Future<MoneroWallet> create(MoneroNewWalletCredentials credentials, {bool? isTestnet}) async {
|
||||
@override
|
||||
Future<MoneroWallet> create(MoneroNewWalletCredentials credentials,
|
||||
{bool? isTestnet}) async {
|
||||
try {
|
||||
final path = await pathForWallet(name: credentials.name, type: getType());
|
||||
|
||||
if (credentials.isPolyseed) {
|
||||
if (credentials.seedType == MoneroSeedType.bip39) {
|
||||
return _restoreFromBip39(
|
||||
path: path,
|
||||
password: credentials.password!,
|
||||
mnemonic: credentials.mnemonic ?? getBip39Seed(),
|
||||
passphrase: credentials.passphrase,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
);
|
||||
}
|
||||
|
||||
if (credentials.seedType == MoneroSeedType.polyseed) {
|
||||
final polyseed = Polyseed.create();
|
||||
final lang = PolyseedLang.getByEnglishName(credentials.language);
|
||||
|
||||
if (credentials.passphrase != null) polyseed.crypt(credentials.passphrase!);
|
||||
if (credentials.passphrase != null)
|
||||
polyseed.crypt(credentials.passphrase!);
|
||||
|
||||
final heightOverride =
|
||||
getMoneroHeigthByDate(date: DateTime.now().subtract(Duration(days: 2)));
|
||||
final heightOverride = getMoneroHeigthByDate(
|
||||
date: DateTime.now().subtract(Duration(days: 2)));
|
||||
|
||||
return _restoreFromPolyseed(
|
||||
path, credentials.password!, polyseed, credentials.walletInfo!, lang,
|
||||
return _restoreFromPolyseed(path, credentials.password!, polyseed,
|
||||
credentials.walletInfo!, lang,
|
||||
overrideHeight: heightOverride, passphrase: credentials.passphrase);
|
||||
}
|
||||
|
||||
await monero_wallet_manager.createWallet(
|
||||
path: path, password: credentials.password!, language: credentials.language, passphrase: credentials.passphrase??"");
|
||||
path: path,
|
||||
password: credentials.password!,
|
||||
language: credentials.language,
|
||||
passphrase: credentials.passphrase ?? "");
|
||||
final wallet = MoneroWallet(
|
||||
walletInfo: credentials.walletInfo!,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
|
@ -145,7 +171,8 @@ class MoneroWalletService extends WalletService<
|
|||
}
|
||||
|
||||
@override
|
||||
Future<MoneroWallet> openWallet(String name, String password, {OpenWalletTry openWalletTry = OpenWalletTry.initial}) async {
|
||||
Future<MoneroWallet> openWallet(String name, String password,
|
||||
{OpenWalletTry openWalletTry = OpenWalletTry.initial}) async {
|
||||
try {
|
||||
final path = await pathForWallet(name: name, type: getType());
|
||||
|
||||
|
@ -303,8 +330,28 @@ class MoneroWalletService extends WalletService<
|
|||
rethrow;
|
||||
}
|
||||
|
||||
try {
|
||||
if (isBip39Seed(credentials.mnemonic)) {
|
||||
final path =
|
||||
await pathForWallet(name: credentials.name, type: getType());
|
||||
|
||||
return _restoreFromBip39(
|
||||
path: path,
|
||||
password: credentials.password!,
|
||||
mnemonic: credentials.mnemonic,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
overrideHeight: credentials.height!,
|
||||
passphrase: credentials.passphrase,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
printV("Bip39 restore failed: $e");
|
||||
rethrow;
|
||||
}
|
||||
|
||||
try {
|
||||
final path = await pathForWallet(name: credentials.name, type: getType());
|
||||
|
||||
monero_wallet_manager.restoreFromSeed(
|
||||
path: path,
|
||||
password: credentials.password!,
|
||||
|
@ -325,6 +372,50 @@ class MoneroWalletService extends WalletService<
|
|||
}
|
||||
}
|
||||
|
||||
Future<MoneroWallet> _restoreFromBip39({
|
||||
required String path,
|
||||
required String password,
|
||||
required String mnemonic,
|
||||
required WalletInfo walletInfo,
|
||||
String? passphrase,
|
||||
int? overrideHeight,
|
||||
}) async {
|
||||
walletInfo.derivationInfo = DerivationInfo(
|
||||
derivationType: DerivationType.bip39,
|
||||
derivationPath: "m/44'/128'/0'/0/0",
|
||||
);
|
||||
|
||||
final legacyMnemonic =
|
||||
getLegacySeedFromBip39(mnemonic, passphrase: passphrase ?? "");
|
||||
final height =
|
||||
overrideHeight ?? getMoneroHeigthByDate(date: DateTime.now());
|
||||
|
||||
walletInfo.isRecovery = true;
|
||||
walletInfo.restoreHeight = height;
|
||||
|
||||
monero_wallet_manager.restoreFromSeed(
|
||||
path: path,
|
||||
password: password,
|
||||
passphrase: '',
|
||||
seed: legacyMnemonic,
|
||||
restoreHeight: height,
|
||||
);
|
||||
|
||||
monero.Wallet_setCacheAttribute(wptr!,
|
||||
key: "cakewallet.seed.bip39", value: mnemonic);
|
||||
|
||||
monero.Wallet_store(wptr!);
|
||||
|
||||
final wallet = MoneroWallet(
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
password: password,
|
||||
);
|
||||
await wallet.init();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
Future<MoneroWallet> restoreFromPolyseed(
|
||||
MoneroRestoreWalletFromSeedCredentials credentials) async {
|
||||
try {
|
||||
|
@ -344,23 +435,21 @@ class MoneroWalletService extends WalletService<
|
|||
}
|
||||
}
|
||||
|
||||
Future<MoneroWallet> _restoreFromPolyseed(
|
||||
String path, String password, Polyseed polyseed, WalletInfo walletInfo, PolyseedLang lang,
|
||||
Future<MoneroWallet> _restoreFromPolyseed(String path, String password,
|
||||
Polyseed polyseed, WalletInfo walletInfo, PolyseedLang lang,
|
||||
{PolyseedCoin coin = PolyseedCoin.POLYSEED_MONERO,
|
||||
int? overrideHeight,
|
||||
String? passphrase}) async {
|
||||
|
||||
if (polyseed.isEncrypted == false &&
|
||||
(passphrase??'') != "") {
|
||||
if (polyseed.isEncrypted == false && (passphrase ?? '') != "") {
|
||||
// Fallback to the different passphrase offset method, when a passphrase
|
||||
// was provided but the polyseed is not encrypted.
|
||||
monero_wallet_manager.restoreWalletFromPolyseedWithOffset(
|
||||
path: path,
|
||||
password: password,
|
||||
seed: polyseed.encode(lang, coin),
|
||||
seedOffset: passphrase??'',
|
||||
language: "English");
|
||||
|
||||
path: path,
|
||||
password: password,
|
||||
seed: polyseed.encode(lang, coin),
|
||||
seedOffset: passphrase ?? '',
|
||||
language: "English");
|
||||
|
||||
final wallet = MoneroWallet(
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
|
@ -437,7 +526,8 @@ class MoneroWalletService extends WalletService<
|
|||
|
||||
if (walletFilesExist(path)) await repairOldAndroidWallet(name);
|
||||
|
||||
await monero_wallet_manager.openWalletAsync({'path': path, 'password': password});
|
||||
await monero_wallet_manager
|
||||
.openWalletAsync({'path': path, 'password': password});
|
||||
final walletInfo = walletInfoSource.values
|
||||
.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
|
||||
final wallet = MoneroWallet(
|
||||
|
|
|
@ -5,18 +5,23 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: "4897882604d919befd350648c7f91926a9d5de99e67b455bf0917cc2362f4bb8"
|
||||
sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "47.0.0"
|
||||
version: "76.0.0"
|
||||
_macros:
|
||||
dependency: transitive
|
||||
description: dart
|
||||
source: sdk
|
||||
version: "0.3.3"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: "690e335554a8385bc9d787117d9eb52c0c03ee207a607e593de3c9d71b1cfe80"
|
||||
sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.7.0"
|
||||
version: "6.11.0"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -41,6 +46,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.11.0"
|
||||
bip32:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: bip32
|
||||
sha256: "54787cd7a111e9d37394aabbf53d1fc5e2e0e0af2cd01c459147a97c0e3f8a97"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
bip39:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: bip39
|
||||
sha256: de1ee27ebe7d96b84bb3a04a4132a0a3007dcdd5ad27dd14aa87a29d97c45edc
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.6"
|
||||
blockchain_utils:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -66,6 +87,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
bs58check:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: bs58check
|
||||
sha256: c4a164d42b25c2f6bc88a8beccb9fc7d01440f3c60ba23663a20a70faf484ea9
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
build:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -94,10 +123,10 @@ packages:
|
|||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_resolvers
|
||||
sha256: "687cf90a3951affac1bd5f9ecb5e3e90b60487f3d9cdc359bb310f8876bb02a6"
|
||||
sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.10"
|
||||
version: "2.4.4"
|
||||
build_runner:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
@ -222,10 +251,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: dart_style
|
||||
sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4"
|
||||
sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.4"
|
||||
version: "2.3.8"
|
||||
dbus:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -348,6 +377,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.0"
|
||||
hex:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: hex
|
||||
sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
hive:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -360,10 +397,10 @@ packages:
|
|||
dependency: "direct dev"
|
||||
description:
|
||||
name: hive_generator
|
||||
sha256: "81fd20125cb2ce8fd23623d7744ffbaf653aae93706c9bd3bf7019ea0ace3938"
|
||||
sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.3"
|
||||
version: "2.0.1"
|
||||
http:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -468,6 +505,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
macros:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: macros
|
||||
sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.3-main.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -512,10 +557,18 @@ packages:
|
|||
dependency: "direct dev"
|
||||
description:
|
||||
name: mobx_codegen
|
||||
sha256: d4beb9cea4b7b014321235f8fdc7c2193ee0fe1d1198e9da7403f8bc85c4407c
|
||||
sha256: "990da80722f7d7c0017dec92040b31545d625b15d40204c36a1e63d167c73cdc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
version: "2.7.0"
|
||||
mockito:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: mockito
|
||||
sha256: f99d8d072e249f719a5531735d146d8cf04c580d93920b04de75bef6dfb2daf6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.4.5"
|
||||
monero:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -735,18 +788,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: source_gen
|
||||
sha256: "2d79738b6bbf38a43920e2b8d189e9a3ce6cc201f4b8fc76be5e4fe377b1c38d"
|
||||
sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.6"
|
||||
version: "1.5.0"
|
||||
source_helper:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_helper
|
||||
sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f"
|
||||
sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.3"
|
||||
version: "1.3.5"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -924,5 +977,5 @@ packages:
|
|||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.5.0 <4.0.0"
|
||||
dart: ">=3.6.0 <4.0.0"
|
||||
flutter: ">=3.24.0"
|
||||
|
|
|
@ -12,6 +12,8 @@ environment:
|
|||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
bip39: ^1.0.6
|
||||
bip32: ^2.0.0
|
||||
ffi: ^2.0.1
|
||||
http: ^1.1.0
|
||||
path_provider: ^2.0.11
|
||||
|
@ -36,7 +38,8 @@ dev_dependencies:
|
|||
build_runner: ^2.4.7
|
||||
build_resolvers: ^2.0.9
|
||||
mobx_codegen: ^2.0.7
|
||||
hive_generator: ^1.1.3
|
||||
mockito: ^5.4.5
|
||||
hive_generator: ^2.0.1
|
||||
|
||||
dependency_overrides:
|
||||
watcher: ^1.1.0
|
||||
|
|
39
cw_monero/test/bip39_seed_test.dart
Normal file
39
cw_monero/test/bip39_seed_test.dart
Normal file
|
@ -0,0 +1,39 @@
|
|||
import 'package:cw_monero/bip39_seed.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
group("Exodus Style bip39", () {
|
||||
group("Test Wallet 1", () {
|
||||
final bip39Seed = 'meadow tip best belt boss eyebrow control affair eternal piece very shiver';
|
||||
final expectedLegacySeed0 = "tasked eight afraid laboratory tail feline rift reinvest vane cafe bailed foggy dormant paper jigsaw king hazard suture king dapper dummy jolted dating dwindling king";
|
||||
final expectedLegacySeed1 = "palace pairing axes mohawk rekindle excess awful juvenile shipped talent nibs efficient dapper biggest swung fight pact innocent emerge issued titans affair nearby noises emerge";
|
||||
|
||||
test("Get legacy Seed from bip39", () {
|
||||
final legacySeed = getLegacySeedFromBip39(bip39Seed);
|
||||
expect(legacySeed, expectedLegacySeed0);
|
||||
});
|
||||
|
||||
test("Get legacy Seed from bip39 with account index", () {
|
||||
final legacySeed = getLegacySeedFromBip39(bip39Seed, accountIndex: 1);
|
||||
expect(legacySeed, expectedLegacySeed1);
|
||||
});
|
||||
});
|
||||
|
||||
group("Test Wallet 2", () {
|
||||
final bip39Seed = "color ranch color remove subway public water embrace before begin liberty fault";
|
||||
final expectedLegacySeed0 = "somewhere problems gauze gigantic intended foxes upcoming saved waffle pipeline lurk bogeys empty wipeout abbey italics novelty tucks rafts elite lunar obnoxious awful bugs elite";
|
||||
final expectedLegacySeed1 = "playful toxic wildly eluded mesh fainted february mugged maps repent vigilant hitched seventh threaten clue fetches sample diet number alkaline future cottage tuition vegan alkaline";
|
||||
|
||||
test("Get legacy Seed from bip39", () {
|
||||
final legacySeed = getLegacySeedFromBip39(bip39Seed);
|
||||
expect(legacySeed, expectedLegacySeed0);
|
||||
});
|
||||
|
||||
test("Get legacy Seed from bip39 with account index", () {
|
||||
final legacySeed = getLegacySeedFromBip39(bip39Seed, accountIndex: 1);
|
||||
expect(legacySeed, expectedLegacySeed1);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
29
cw_monero/test/mock/path_provider.dart
Normal file
29
cw_monero/test/mock/path_provider.dart
Normal file
|
@ -0,0 +1,29 @@
|
|||
import 'package:mockito/mockito.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
|
||||
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
||||
|
||||
class MockPathProviderPlatform extends Mock
|
||||
with MockPlatformInterfaceMixin
|
||||
implements PathProviderPlatform {
|
||||
Future<String> getTemporaryPath() => throw UnimplementedError();
|
||||
|
||||
Future<String> getApplicationSupportPath() => throw UnimplementedError();
|
||||
|
||||
Future<String> getLibraryPath() => throw UnimplementedError();
|
||||
|
||||
Future<String> getApplicationDocumentsPath() async => "./test/data";
|
||||
|
||||
Future<String> getExternalStoragePath() => throw UnimplementedError();
|
||||
|
||||
Future<List<String>> getExternalCachePaths() => throw UnimplementedError();
|
||||
|
||||
Future<String> getDownloadsPath() => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Future<String?> getApplicationCachePath() => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Future<List<String>?> getExternalStoragePaths({StorageDirectory? type}) =>
|
||||
throw UnimplementedError();
|
||||
}
|
148
cw_monero/test/monero_wallet_service_test.dart
Normal file
148
cw_monero/test/monero_wallet_service_test.dart
Normal file
|
@ -0,0 +1,148 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_monero/monero_wallet_service.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
|
||||
|
||||
import 'mock/path_provider.dart';
|
||||
import 'utils/setup_monero_c.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
group("MoneroWalletService Tests", () {
|
||||
Hive.init('./test/data/db');
|
||||
late MoneroWalletService walletService;
|
||||
late File moneroCBinary;
|
||||
|
||||
setUpAll(() async {
|
||||
PathProviderPlatform.instance = MockPathProviderPlatform();
|
||||
|
||||
final Box<WalletInfo> walletInfoSource =
|
||||
await Hive.openBox('testWalletInfo');
|
||||
final Box<UnspentCoinsInfo> unspentCoinsInfoSource =
|
||||
await Hive.openBox('testUnspentCoinsInfo');
|
||||
|
||||
walletService = MoneroWalletService(walletInfoSource, unspentCoinsInfoSource);
|
||||
moneroCBinary = getMoneroCBinary().copySync(moneroCBinaryName);
|
||||
});
|
||||
|
||||
tearDownAll(() {
|
||||
Directory('./test/data').deleteSync(recursive: true);
|
||||
moneroCBinary.deleteSync();
|
||||
});
|
||||
|
||||
group("Create wallet", () {
|
||||
test("Create Legacy Wallet", () async {
|
||||
final credentials = _getTestCreateCredentials(
|
||||
name: 'Create Wallet LS',
|
||||
language: 'English',
|
||||
seedType: MoneroSeedType.legacy);
|
||||
final wallet = await walletService.create(credentials);
|
||||
|
||||
expect(wallet.seed.split(" ").length, 25);
|
||||
expect(wallet.restoreHeight, greaterThan(3000000));
|
||||
});
|
||||
|
||||
test("Create Polyseed Wallet", () async {
|
||||
final credentials = _getTestCreateCredentials(
|
||||
name: 'Create Wallet PS',
|
||||
language: 'English',
|
||||
seedType: MoneroSeedType.polyseed);
|
||||
final wallet = await walletService.create(credentials);
|
||||
|
||||
expect(wallet.seed.split(" ").length, 16);
|
||||
expect(wallet.restoreHeight, greaterThan(3000000));
|
||||
});
|
||||
|
||||
test("Create Bip39 Wallet", () async {
|
||||
final credentials = _getTestCreateCredentials(
|
||||
name: 'Create Wallet BS',
|
||||
language: 'English',
|
||||
seedType: MoneroSeedType.bip39);
|
||||
final wallet = await walletService.create(credentials);
|
||||
|
||||
expect(wallet.seed.split(" ").length, 12);
|
||||
expect(wallet.restoreHeight, greaterThan(3000000));
|
||||
});
|
||||
});
|
||||
|
||||
group("Restore wallet", () {
|
||||
test('Legacy Seed', () async {
|
||||
final credentials = _getTestRestoreCredentials(
|
||||
name: 'Test Wallet LS',
|
||||
mnemonic:
|
||||
'ability pockets lordship tomorrow gypsy match neutral uncle avatar betting bicycle junk unzip pyramid lynx mammal edgy empty uneven knowledge juvenile wiring paradise psychic betting',
|
||||
);
|
||||
|
||||
final wallet = await walletService.restoreFromSeed(credentials);
|
||||
expect(wallet.walletAddresses.primaryAddress,
|
||||
'48tLyQXpcwt8w6uKHyb5Zs3vdnoDWAEKFQr1c198o7aX9dBzXP3BTSMVsDiuH3ozDCNqwojb4vNeQZf7xg6URimDLaNtGSN');
|
||||
});
|
||||
|
||||
test('Bip39 Seed', () async {
|
||||
final credentials = _getTestRestoreCredentials(
|
||||
name: 'Test Wallet BS',
|
||||
mnemonic:
|
||||
'color ranch color remove subway public water embrace before begin liberty fault');
|
||||
|
||||
final wallet = await walletService.restoreFromSeed(credentials);
|
||||
expect(wallet.walletAddresses.primaryAddress,
|
||||
'49MggvPosJugF8Zq7WAKbsSchz6vbyL6YiUxM4ryfGQDXphs6wiWiXLFWCSshnLPcceGTWUaKfWWMHQAAKESV3TQJVQsL9a');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
MoneroRestoreWalletFromSeedCredentials _getTestRestoreCredentials({
|
||||
required String name,
|
||||
required String mnemonic,
|
||||
}) {
|
||||
final credentials = MoneroRestoreWalletFromSeedCredentials(
|
||||
name: name, mnemonic: mnemonic, passphrase: '', password: "test");
|
||||
|
||||
credentials.walletInfo = WalletInfo.external(
|
||||
id: WalletBase.idFor(name, WalletType.monero),
|
||||
name: name,
|
||||
type: WalletType.monero,
|
||||
isRecovery: true,
|
||||
restoreHeight: credentials.height ?? 0,
|
||||
date: DateTime.now(),
|
||||
path: '',
|
||||
dirPath: '',
|
||||
address: '',
|
||||
);
|
||||
return credentials;
|
||||
}
|
||||
|
||||
MoneroNewWalletCredentials _getTestCreateCredentials({
|
||||
required String name,
|
||||
required String language,
|
||||
required MoneroSeedType seedType,
|
||||
String? mnemonic,
|
||||
}) {
|
||||
final credentials = MoneroNewWalletCredentials(
|
||||
name: name,
|
||||
language: language,
|
||||
seedType: seedType,
|
||||
password: "test",
|
||||
mnemonic: mnemonic,
|
||||
passphrase: '',
|
||||
);
|
||||
|
||||
credentials.walletInfo = WalletInfo.external(
|
||||
id: WalletBase.idFor(name, WalletType.monero),
|
||||
name: name,
|
||||
type: WalletType.monero,
|
||||
isRecovery: false,
|
||||
restoreHeight: credentials.height ?? 0,
|
||||
date: DateTime.now(),
|
||||
path: '',
|
||||
dirPath: '',
|
||||
address: '',
|
||||
);
|
||||
return credentials;
|
||||
}
|
16
cw_monero/test/utils/setup_monero_c.dart
Normal file
16
cw_monero/test/utils/setup_monero_c.dart
Normal file
|
@ -0,0 +1,16 @@
|
|||
import 'dart:io';
|
||||
|
||||
File getMoneroCBinary() {
|
||||
if (Platform.isWindows)
|
||||
return File(
|
||||
'../scripts/monero_c/release/monero/x86_64-w64-mingw32_libwallet2_api_c.dll');
|
||||
if (Platform.isMacOS) return File('../macos/monero_libwallet2_api_c.dylib');
|
||||
return File('../scripts/monero_c/release/monero/x86_64-linux-gnu_libwallet2_api_c.so');
|
||||
}
|
||||
|
||||
String get moneroCBinaryName {
|
||||
if (Platform.isWindows)
|
||||
return "monero_libwallet2_api_c.dll";
|
||||
if (Platform.isMacOS) return "monero_libwallet2_api_c.dylib";
|
||||
return "/usr/lib/monero_libwallet2_api_c.so";
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue