Fix issues with Creating Electrum and Restoring Bip39

This commit is contained in:
OmarHatem 2024-04-26 01:30:59 +03:00
parent 02d719431a
commit 2cc115e557
16 changed files with 92 additions and 62 deletions

View file

@ -1,11 +1,11 @@
import 'package:cw_core/wallet_info.dart';
Map<DerivationType, List<DerivationInfo>> bitcoin_derivations = {
DerivationType.electrum2: [
DerivationType.electrum: [
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/0'/1",
description: "Electrum 2",
derivationType: DerivationType.electrum,
derivationPath: "m/0'/0",
description: "Electrum",
script_type: "p2wpkh",
),
],

View file

@ -65,8 +65,7 @@ String bufferToBin(Uint8List data) {
return q2;
}
String encode(Uint8List originalData) {
final data = Uint8List.fromList(originalData); // Create a modifiable copy
String encode(Uint8List data) {
final dataBitLen = data.length * 8;
final wordBitLen = logBase(wordlist.length, 2).ceil();
final wordCount = (dataBitLen / wordBitLen).floor();
@ -98,9 +97,10 @@ Future<String> generateElectrumMnemonic({int strength = 264, String prefix = seg
var result = '';
do {
final originalBytes = await secRandom(byteCount);
// create a modifiable copy, however I'm not sure why this is necessary
final bytes = Uint8List.fromList(originalBytes);
// final originalBytes = await secRandom(byteCount);
// // create a modifiable copy, however I'm not sure why this is necessary
// final bytes = Uint8List.fromList(originalBytes);
final bytes = await secRandom(byteCount);
maskBytes(bytes, strength);
result = encode(bytes);
} while (!prefixMatches(result, [prefix]).first);

View file

@ -89,7 +89,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
case DerivationType.bip39:
seedBytes = await bip39.mnemonicToSeed(mnemonic);
break;
case DerivationType.electrum2:
case DerivationType.electrum:
default:
seedBytes = await mnemonicToSeedBytes(mnemonic);
break;
@ -121,7 +121,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
final snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password, network);
walletInfo.derivationInfo ??= DerivationInfo(
derivationType: snp.derivationType ?? DerivationType.electrum2,
derivationType: snp.derivationType ?? DerivationType.electrum,
derivationPath: snp.derivationPath,
);
@ -131,7 +131,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
late Uint8List seedBytes;
switch (walletInfo.derivationInfo!.derivationType) {
case DerivationType.electrum2:
case DerivationType.electrum:
seedBytes = await mnemonicToSeedBytes(snp.mnemonic);
break;
case DerivationType.bip39:

View file

@ -10,10 +10,6 @@ class BitcoinNewWalletCredentials extends WalletCredentials {
: super(
name: name,
walletInfo: walletInfo,
derivationInfo: DerivationInfo(
derivationType: derivationType,
derivationPath: derivationPath,
),
);
}

View file

@ -1,17 +1,11 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:cw_bitcoin/address_to_output_script.dart';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import 'package:cw_bitcoin/mnemonic_is_incorrect_exception.dart';
import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
import 'package:cw_bitcoin/electrum.dart';
import 'package:cw_bitcoin/script_hash.dart';
import 'package:cw_bitcoin/utils.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_service.dart';
import 'package:cw_bitcoin/bitcoin_wallet.dart';
import 'package:cw_core/pathForWallet.dart';
@ -19,10 +13,6 @@ import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:hive/hive.dart';
import 'package:collection/collection.dart';
import 'package:mobx/mobx.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:cw_bitcoin/bitcoin_derivations.dart';
import 'package:bip32/bip32.dart' as bip32;
import 'package:bip39/bip39.dart' as bip39;
class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
@ -41,7 +31,7 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
credentials.walletInfo?.network = network.value;
final wallet = await BitcoinWalletBase.create(
mnemonic: await generateElectrumMnemonic(strength: 132),
mnemonic: await generateElectrumMnemonic(),
password: credentials.password!,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource,
@ -117,7 +107,7 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
@override
Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials,
{bool? isTestnet}) async {
if (!validateMnemonic(credentials.mnemonic)) {
if (!validateMnemonic(credentials.mnemonic) && !bip39.validateMnemonic(credentials.mnemonic)) {
throw BitcoinMnemonicIsIncorrectException();
}

View file

@ -61,7 +61,8 @@ abstract class ElectrumWalletBase
CryptoCurrency? currency})
: hd = currency == CryptoCurrency.bch
? bitcoinCashHDWallet(seedBytes)
: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/0"),
: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType)
.derivePath(walletInfo.derivationInfo?.derivationPath ?? "m/0'/0"),
syncStatus = NotConnectedSyncStatus(),
_password = password,
_feeRates = <int>[],
@ -592,6 +593,8 @@ abstract class ElectrumWalletBase
? SegwitAddresType.p2wpkh.toString()
: walletInfo.addressPageType.toString(),
'balance': balance[currency]?.toJSON(),
'derivationTypeIndex': walletInfo.derivationInfo?.derivationType?.index,
'derivationPath': walletInfo.derivationInfo?.derivationPath,
});
int feeRate(TransactionPriority priority) {

View file

@ -51,8 +51,9 @@ class ElectrumWalletSnapshot {
var regularAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
var changeAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
final derivationType = data['derivationType'] as DerivationType? ?? DerivationType.bip39;
final derivationPath = data['derivationPath'] as String? ?? "m/0'/1";
final derivationType =
DerivationType.values[(data['derivationTypeIndex'] as int?) ?? DerivationType.electrum.index];
final derivationPath = data['derivationPath'] as String? ?? "m/0'/0";
try {
regularAddressIndexByType = {

View file

@ -7,7 +7,7 @@ abstract class WalletCredentials {
this.seedPhraseLength,
this.walletInfo,
this.password,
DerivationInfo? derivationInfo,
this.derivationInfo,
}) {
if (this.walletInfo != null && derivationInfo != null) {
this.walletInfo!.derivationInfo = derivationInfo;
@ -19,4 +19,5 @@ abstract class WalletCredentials {
int? seedPhraseLength;
String? password;
WalletInfo? walletInfo;
DerivationInfo? derivationInfo;
}

View file

@ -17,9 +17,7 @@ enum DerivationType {
@HiveField(3)
bip39,
@HiveField(4)
electrum1,
@HiveField(5)
electrum2,
electrum,
}
@HiveType(typeId: DerivationInfo.typeId)
class DerivationInfo extends HiveObject {
@ -28,7 +26,7 @@ class DerivationInfo extends HiveObject {
this.derivationPath,
this.balance = "",
this.address = "",
this.height = 0,
this.transactionsCount = 0,
this.script_type,
this.description,
});
@ -37,7 +35,7 @@ class DerivationInfo extends HiveObject {
String balance;
String address;
int height;
int transactionsCount;
DerivationType? derivationType;
String? derivationPath;
final String? script_type;

View file

@ -260,10 +260,10 @@ class CWBitcoin extends Bitcoin {
Future<List<DerivationType>> compareDerivationMethods(
{required String mnemonic, required Node node}) async {
if (await checkIfMnemonicIsElectrum2(mnemonic)) {
return [DerivationType.electrum2];
return [DerivationType.electrum];
}
return [DerivationType.bip39, DerivationType.electrum2];
return [DerivationType.bip39, DerivationType.electrum];
}
int _countOccurrences(String str, String charToCount) {
@ -286,7 +286,7 @@ class CWBitcoin extends Bitcoin {
for (DerivationType dType in bitcoin_derivations.keys) {
late Uint8List seedBytes;
if (dType == DerivationType.electrum2) {
if (dType == DerivationType.electrum) {
seedBytes = await mnemonicToSeedBytes(mnemonic);
} else if (dType == DerivationType.bip39) {
seedBytes = bip39.mnemonicToSeed(mnemonic);
@ -300,7 +300,7 @@ class CWBitcoin extends Bitcoin {
description: dInfo.description,
script_type: dInfo.script_type,
);
var node = bip32.BIP32.fromSeed(seedBytes);
var hd = bip32.BIP32.fromSeed(seedBytes);
String derivationPath = dInfoCopy.derivationPath!;
int derivationDepth = _countOccurrences(derivationPath, "/");
@ -308,29 +308,67 @@ class CWBitcoin extends Bitcoin {
derivationPath += "/0/0";
dInfoCopy.derivationPath = dInfoCopy.derivationPath! + "/0";
}
node = node.derivePath(derivationPath);
hd = hd.derivePath(derivationPath);
// var hd = btc.HDWallet.fromSeed(
// seedBytes,
// network: node.type == WalletType.bitcoin ? btc.bitcoin : litecoinNetwork,
// ).derivePath(dInfoCopy.derivationPath!);
// if (addressType == P2pkhAddressType.p2pkh)
// return generateP2PKHAddress(hd: hd, index: index, network: network);
//
// if (addressType == SegwitAddresType.p2tr)
// return generateP2TRAddress(hd: hd, index: index, network: network);
//
// if (addressType == SegwitAddresType.p2wsh)
// return generateP2WSHAddress(hd: hd, index: index, network: network);
//
// if (addressType == P2shAddressType.p2wpkhInP2sh)
// return generateP2SHAddress(hd: hd, index: index, network: network);
String? address;
switch (dInfoCopy.script_type) {
case "p2wpkh":
// address = generateP2WPKHAddress(
// hd: hd,
// index: 0,
// network: node.type == WalletType.bitcoin
// ? BitcoinNetwork.mainnet
// : LitecoinNetwork.mainnet);
address = btc
.P2WPKH(
data: new btc.PaymentData(pubkey: node.publicKey),
data: new btc.PaymentData(pubkey: hd.publicKey),
network: btc.bitcoin,
)
.data
.address;
break;
case "p2pkh":
default:
// address = generateP2PKHAddress(
// hd: hd,
// index: 0,
// network: node.type == WalletType.bitcoin
// ? BitcoinNetwork.mainnet
// : LitecoinNetwork.mainnet);
address = btc
.P2PKH(
data: new btc.PaymentData(pubkey: node.publicKey),
network: btc.bitcoin,
)
data: new btc.PaymentData(pubkey: hd.publicKey),
network: btc.bitcoin,
)
.data
.address;
break;
// case "p2wpkh-p2sh":
// address = generateP2SHAddress(
// hd: hd,
// index: 0,
// network: node.type == WalletType.bitcoin
// ? BitcoinNetwork.mainnet
// : LitecoinNetwork.mainnet);
// break;
default:
continue;
}
final sh = scriptHash(address!, network: BitcoinNetwork.mainnet);
@ -339,7 +377,7 @@ class CWBitcoin extends Bitcoin {
final balance = await electrumClient.getBalance(sh);
dInfoCopy.balance = balance.entries.first.value.toString();
dInfoCopy.address = address;
dInfoCopy.height = history.length;
dInfoCopy.transactionsCount = history.length;
list.add(dInfoCopy);
} catch (e) {
@ -349,7 +387,7 @@ class CWBitcoin extends Bitcoin {
}
// sort the list such that derivations with the most transactions are first:
list.sort((a, b) => b.height.compareTo(a.height));
list.sort((a, b) => b.transactionsCount.compareTo(a.transactionsCount));
return list;
}

View file

@ -767,7 +767,7 @@ Future<void> updateBtcNanoWalletInfos(Box<WalletInfo> walletsInfoSource) async {
derivationPath: walletInfo.derivationPath,
derivationType: walletInfo.derivationType,
address: walletInfo.address,
height: walletInfo.restoreHeight,
transactionsCount: walletInfo.restoreHeight,
);
await walletInfo.save();
}

View file

@ -101,7 +101,7 @@ class WalletRestoreChooseDerivationPage extends BasePage {
),
),
Text(
"${S.current.transactions}: ${derivation.height}",
"${S.current.transactions}: ${derivation.transactionsCount}",
style: Theme.of(context).primaryTextTheme.labelMedium!.copyWith(
fontSize: 16,
fontWeight: FontWeight.w500,

View file

@ -360,7 +360,7 @@ class WalletRestorePage extends BasePage {
int derivationsWithHistory = 0;
int derivationWithHistoryIndex = 0;
for (int i = 0; i < derivations.length; i++) {
if (derivations[i].height > 0) {
if (derivations[i].transactionsCount > 0) {
derivationsWithHistory++;
derivationWithHistoryIndex = i;
}

View file

@ -101,7 +101,7 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
name: name,
mnemonic: restoreWallet.mnemonicSeed ?? '',
password: password,
derivationType: derivationInfo.derivationType!,
derivationType: derivationInfo!.derivationType!,
derivationPath: derivationInfo.derivationPath!,
);
case WalletType.bitcoinCash:
@ -115,7 +115,7 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
name: name,
mnemonic: restoreWallet.mnemonicSeed ?? '',
password: password,
derivationType: derivationInfo.derivationType!);
derivationType: derivationInfo!.derivationType!);
case WalletType.polygon:
return polygon!.createPolygonRestoreWalletFromSeedCredentials(
name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);

View file

@ -71,9 +71,9 @@ abstract class WalletCreationVMBase with Store {
dirPath: dirPath,
address: '',
showIntroCakePayCard: (!walletCreationService.typeExists(type)) && type != WalletType.haven,
derivationInfo: getDefaultDerivation(),
derivationInfo: credentials.derivationInfo ?? getDefaultDerivation(),
);
credentials.walletInfo = walletInfo;
final wallet = restoreWallet != null
? await processFromRestoredWallet(credentials, restoreWallet)
@ -89,7 +89,7 @@ abstract class WalletCreationVMBase with Store {
}
}
DerivationInfo getDefaultDerivation() {
DerivationInfo? getDefaultDerivation() {
switch (this.type) {
case WalletType.nano:
return DerivationInfo(
@ -97,11 +97,12 @@ abstract class WalletCreationVMBase with Store {
);
case WalletType.bitcoin:
case WalletType.litecoin:
default:
return DerivationInfo(
derivationType: DerivationType.electrum2,
derivationPath: "m/0'/1",
derivationType: DerivationType.electrum,
derivationPath: "m/0'/0",
);
default:
return null;
}
}

View file

@ -204,6 +204,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
switch (walletType) {
case WalletType.bitcoin:
case WalletType.litecoin:
String? mnemonic = credentials['seed'] as String?;
return bitcoin!.getDerivationsFromMnemonic(mnemonic: mnemonic!, node: node);
case WalletType.nano:
@ -226,7 +227,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
derivationType: DerivationType.nano,
balance: nanoUtil!.getRawAsUsableString(standardInfo!.balance, nanoUtil!.rawPerNano),
address: standardInfo.address!,
height: standardInfo.confirmationHeight,
transactionsCount: standardInfo.confirmationHeight,
));
}
@ -235,7 +236,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
derivationType: DerivationType.bip39,
balance: nanoUtil!.getRawAsUsableString(bip39Info!.balance, nanoUtil!.rawPerNano),
address: bip39Info.address!,
height: bip39Info.confirmationHeight,
transactionsCount: bip39Info.confirmationHeight,
));
}
break;
@ -254,6 +255,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
switch (type) {
case WalletType.bitcoin:
case WalletType.litecoin:
return bitcoin!.compareDerivationMethods(mnemonic: mnemonic!, node: node);
case WalletType.nano:
return nanoUtil!.compareDerivationMethods(