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

View file

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

View file

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

View file

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

View file

@ -1,17 +1,11 @@
import 'dart:io'; 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:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import 'package:cw_bitcoin/mnemonic_is_incorrect_exception.dart'; import 'package:cw_bitcoin/mnemonic_is_incorrect_exception.dart';
import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.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/node.dart';
import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/wallet_base.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_core/wallet_service.dart';
import 'package:cw_bitcoin/bitcoin_wallet.dart'; import 'package:cw_bitcoin/bitcoin_wallet.dart';
import 'package:cw_core/pathForWallet.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:cw_core/wallet_type.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:collection/collection.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; import 'package:bip39/bip39.dart' as bip39;
class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials, class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
@ -41,7 +31,7 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
credentials.walletInfo?.network = network.value; credentials.walletInfo?.network = network.value;
final wallet = await BitcoinWalletBase.create( final wallet = await BitcoinWalletBase.create(
mnemonic: await generateElectrumMnemonic(strength: 132), mnemonic: await generateElectrumMnemonic(),
password: credentials.password!, password: credentials.password!,
walletInfo: credentials.walletInfo!, walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource, unspentCoinsInfo: unspentCoinsInfoSource,
@ -117,7 +107,7 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
@override @override
Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials, Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials,
{bool? isTestnet}) async { {bool? isTestnet}) async {
if (!validateMnemonic(credentials.mnemonic)) { if (!validateMnemonic(credentials.mnemonic) && !bip39.validateMnemonic(credentials.mnemonic)) {
throw BitcoinMnemonicIsIncorrectException(); throw BitcoinMnemonicIsIncorrectException();
} }

View file

@ -61,7 +61,8 @@ abstract class ElectrumWalletBase
CryptoCurrency? currency}) CryptoCurrency? currency})
: hd = currency == CryptoCurrency.bch : hd = currency == CryptoCurrency.bch
? bitcoinCashHDWallet(seedBytes) ? 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(), syncStatus = NotConnectedSyncStatus(),
_password = password, _password = password,
_feeRates = <int>[], _feeRates = <int>[],
@ -592,6 +593,8 @@ abstract class ElectrumWalletBase
? SegwitAddresType.p2wpkh.toString() ? SegwitAddresType.p2wpkh.toString()
: walletInfo.addressPageType.toString(), : walletInfo.addressPageType.toString(),
'balance': balance[currency]?.toJSON(), 'balance': balance[currency]?.toJSON(),
'derivationTypeIndex': walletInfo.derivationInfo?.derivationType?.index,
'derivationPath': walletInfo.derivationInfo?.derivationPath,
}); });
int feeRate(TransactionPriority priority) { int feeRate(TransactionPriority priority) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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