feat: all address derivations

This commit is contained in:
Rafael Saes 2024-11-04 19:29:25 -03:00
parent 4a4250a905
commit a3e131d369
18 changed files with 330 additions and 157 deletions

View file

@ -1,6 +1,7 @@
import 'dart:convert';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
abstract class BaseBitcoinAddressRecord {
BaseBitcoinAddressRecord(
@ -63,11 +64,13 @@ abstract class BaseBitcoinAddressRecord {
class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
final BitcoinDerivationInfo derivationInfo;
final CWBitcoinDerivationType derivationType;
BitcoinAddressRecord(
super.address, {
required super.index,
required this.derivationInfo,
required this.derivationType,
super.isHidden,
super.isChange = false,
super.txCount = 0,
@ -94,6 +97,7 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
derivationInfo: BitcoinDerivationInfo.fromJSON(
decoded['derivationInfo'] as Map<String, dynamic>,
),
derivationType: CWBitcoinDerivationType.values[decoded['derivationType'] as int],
isHidden: decoded['isHidden'] as bool? ?? false,
isChange: decoded['isChange'] as bool? ?? false,
isUsed: decoded['isUsed'] as bool? ?? false,
@ -115,6 +119,7 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
'address': address,
'index': index,
'derivationInfo': derivationInfo.toJSON(),
'derivationType': derivationType.index,
'isHidden': isHidden,
'isChange': isChange,
'isUsed': isUsed,

View file

@ -2,16 +2,14 @@ import 'dart:async';
import 'dart:convert';
import 'dart:isolate';
import 'package:bip39/bip39.dart' as bip39;
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import 'package:cw_bitcoin/psbt_transaction_builder.dart';
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
import 'package:cw_bitcoin/bitcoin_unspent.dart';
import 'package:cw_bitcoin/electrum_transaction_info.dart';
// import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_bitcoin/electrum_derivations.dart';
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
@ -60,6 +58,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
int initialSilentAddressIndex = 0,
bool? alwaysScan,
required bool mempoolAPIEnabled,
super.hdWallets,
}) : super(
mnemonic: mnemonic,
passphrase: passphrase,
@ -88,9 +87,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
initialChangeAddressIndex: initialChangeAddressIndex,
initialSilentAddresses: initialSilentAddresses,
initialSilentAddressIndex: initialSilentAddressIndex,
bip32: bip32,
network: networkParam ?? network,
isHardwareWallet: walletInfo.isHardwareWallet,
hdWallets: hdWallets,
);
autorun((_) {
@ -116,15 +115,49 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
required bool mempoolAPIEnabled,
}) async {
late List<int> seedBytes;
final Map<CWBitcoinDerivationType, Bip32Slip10Secp256k1> hdWallets = {};
switch (walletInfo.derivationInfo?.derivationType) {
case DerivationType.bip39:
for (final derivation in walletInfo.derivations ?? <DerivationInfo>[]) {
if (derivation.derivationType == DerivationType.bip39) {
seedBytes = Bip39SeedGenerator.generateFromString(mnemonic, passphrase);
hdWallets[CWBitcoinDerivationType.bip39] = Bip32Slip10Secp256k1.fromSeed(seedBytes);
hdWallets[CWBitcoinDerivationType.old] = hdWallets[CWBitcoinDerivationType.bip39]!;
try {
hdWallets[CWBitcoinDerivationType.old] = Bip32Slip10Secp256k1.fromSeed(
seedBytes,
ElectrumWalletBase.getKeyNetVersion(network ?? BitcoinNetwork.mainnet),
).derivePath(
_hardenedDerivationPath(derivation.derivationPath ?? electrum_path),
) as Bip32Slip10Secp256k1;
} catch (e) {
print("bip39 seed error: $e");
}
break;
case DerivationType.electrum:
default:
seedBytes = ElectrumV2SeedGenerator.generateFromString(mnemonic, passphrase);
} else {
try {
seedBytes = ElectrumV2SeedGenerator.generateFromString(mnemonic, passphrase);
hdWallets[CWBitcoinDerivationType.electrum] = Bip32Slip10Secp256k1.fromSeed(seedBytes);
} catch (e) {
print("electrum_v2 seed error: $e");
try {
seedBytes = ElectrumV1SeedGenerator(mnemonic).generate();
hdWallets[CWBitcoinDerivationType.electrum] = Bip32Slip10Secp256k1.fromSeed(seedBytes);
} catch (e) {
print("electrum_v1 seed error: $e");
}
}
try {
hdWallets[CWBitcoinDerivationType.old] = Bip32Slip10Secp256k1.fromSeed(
seedBytes,
).derivePath(
_hardenedDerivationPath(derivation.derivationPath ?? electrum_path),
) as Bip32Slip10Secp256k1;
} catch (_) {}
break;
}
}
return BitcoinWallet(
@ -144,6 +177,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
addressPageType: addressPageType,
networkParam: network,
mempoolAPIEnabled: mempoolAPIEnabled,
hdWallets: hdWallets,
);
}
@ -200,21 +234,52 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
walletInfo.derivationInfo!.derivationType ??= snp?.derivationType ?? DerivationType.electrum;
List<int>? seedBytes = null;
final Map<CWBitcoinDerivationType, Bip32Slip10Secp256k1> hdWallets = {};
final mnemonic = keysData.mnemonic;
final passphrase = keysData.passphrase;
if (mnemonic != null) {
switch (walletInfo.derivationInfo!.derivationType) {
case DerivationType.electrum:
seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
for (final derivation in walletInfo.derivations ?? <DerivationInfo>[]) {
if (derivation.derivationType == DerivationType.bip39) {
seedBytes = Bip39SeedGenerator.generateFromString(mnemonic, passphrase);
hdWallets[CWBitcoinDerivationType.bip39] = Bip32Slip10Secp256k1.fromSeed(seedBytes);
hdWallets[CWBitcoinDerivationType.old] = hdWallets[CWBitcoinDerivationType.bip39]!;
try {
hdWallets[CWBitcoinDerivationType.old] = Bip32Slip10Secp256k1.fromSeed(
seedBytes,
ElectrumWalletBase.getKeyNetVersion(network),
).derivePath(
_hardenedDerivationPath(derivation.derivationPath ?? electrum_path),
) as Bip32Slip10Secp256k1;
} catch (e) {
print("bip39 seed error: $e");
}
break;
case DerivationType.bip39:
default:
seedBytes = await bip39.mnemonicToSeed(
mnemonic,
passphrase: passphrase ?? '',
);
} else {
try {
seedBytes = ElectrumV2SeedGenerator.generateFromString(mnemonic, passphrase);
hdWallets[CWBitcoinDerivationType.electrum] = Bip32Slip10Secp256k1.fromSeed(seedBytes);
} catch (e) {
print("electrum_v2 seed error: $e");
try {
seedBytes = ElectrumV1SeedGenerator(mnemonic).generate();
hdWallets[CWBitcoinDerivationType.electrum] =
Bip32Slip10Secp256k1.fromSeed(seedBytes);
} catch (e) {
print("electrum_v1 seed error: $e");
}
}
try {
hdWallets[CWBitcoinDerivationType.old] =
Bip32Slip10Secp256k1.fromSeed(seedBytes!).derivePath(
_hardenedDerivationPath(derivation.derivationPath ?? electrum_path),
) as Bip32Slip10Secp256k1;
} catch (_) {}
break;
}
}
}
@ -237,6 +302,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
networkParam: network,
alwaysScan: alwaysScan,
mempoolAPIEnabled: mempoolAPIEnabled,
hdWallets: hdWallets,
);
}
@ -784,6 +850,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
super.syncStatusReaction(syncStatus);
}
}
static String _hardenedDerivationPath(String derivationPath) =>
derivationPath.substring(0, derivationPath.lastIndexOf("'") + 1);
}
Future<void> startRefresh(ScanData scanData) async {

View file

@ -1,4 +1,5 @@
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:mobx/mobx.dart';
@ -10,9 +11,9 @@ class BitcoinWalletAddresses = BitcoinWalletAddressesBase with _$BitcoinWalletAd
abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
BitcoinWalletAddressesBase(
WalletInfo walletInfo, {
required super.bip32,
required super.network,
required super.isHardwareWallet,
required super.hdWallets,
super.initialAddresses,
super.initialRegularAddressIndex,
super.initialChangeAddressIndex,
@ -36,36 +37,61 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
@override
BitcoinBaseAddress generateAddress({
required CWBitcoinDerivationType derivationType,
required bool isChange,
required int index,
required BitcoinAddressType addressType,
required BitcoinDerivationInfo derivationInfo,
}) {
final hdWallet = hdWallets[derivationType]!;
if (derivationType == CWBitcoinDerivationType.old) {
final pub = hdWallet
.childKey(Bip32KeyIndex(isChange ? 1 : 0))
.childKey(Bip32KeyIndex(index))
.publicKey;
switch (addressType) {
case P2pkhAddressType.p2pkh:
return ECPublic.fromBip32(pub).toP2pkhAddress();
case SegwitAddresType.p2tr:
return ECPublic.fromBip32(pub).toP2trAddress();
case SegwitAddresType.p2wsh:
return ECPublic.fromBip32(pub).toP2wshAddress();
case P2shAddressType.p2wpkhInP2sh:
return ECPublic.fromBip32(pub).toP2wpkhInP2sh();
case SegwitAddresType.p2wpkh:
return ECPublic.fromBip32(pub).toP2wpkhAddress();
default:
throw ArgumentError('Invalid address type');
}
}
switch (addressType) {
case P2pkhAddressType.p2pkh:
return P2pkhAddress.fromDerivation(
bip32: bip32,
bip32: hdWallet,
derivationInfo: derivationInfo,
isChange: isChange,
index: index,
);
case SegwitAddresType.p2tr:
return P2trAddress.fromDerivation(
bip32: bip32,
bip32: hdWallet,
derivationInfo: derivationInfo,
isChange: isChange,
index: index,
);
case SegwitAddresType.p2wsh:
return P2wshAddress.fromDerivation(
bip32: bip32,
bip32: hdWallet,
derivationInfo: derivationInfo,
isChange: isChange,
index: index,
);
case P2shAddressType.p2wpkhInP2sh:
return P2shAddress.fromDerivation(
bip32: bip32,
bip32: hdWallet,
derivationInfo: derivationInfo,
isChange: isChange,
index: index,
@ -73,7 +99,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
);
case SegwitAddresType.p2wpkh:
return P2wpkhAddress.fromDerivation(
bip32: bip32,
bip32: hdWallet,
derivationInfo: derivationInfo,
isChange: isChange,
index: index,

View file

@ -26,6 +26,7 @@ class BitcoinRestoreWalletFromSeedCredentials extends WalletCredentials {
required String name,
required String password,
required this.mnemonic,
required super.derivations,
WalletInfo? walletInfo,
String? passphrase,
}) : super(

View file

@ -61,6 +61,7 @@ abstract class ElectrumWalletBase
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required this.network,
required this.encryptionFileUtils,
Map<CWBitcoinDerivationType, Bip32Slip10Secp256k1>? hdWallets,
String? xpub,
String? mnemonic,
List<int>? seedBytes,
@ -71,7 +72,16 @@ abstract class ElectrumWalletBase
CryptoCurrency? currency,
this.alwaysScan,
required this.mempoolAPIEnabled,
}) : bip32 = getAccountHDWallet(currency, network, seedBytes, xpub, walletInfo.derivationInfo),
}) : hdWallets = hdWallets ??
{
CWBitcoinDerivationType.bip39: getAccountHDWallet(
currency,
network,
seedBytes,
xpub,
walletInfo.derivationInfo,
)
},
syncStatus = NotConnectedSyncStatus(),
_password = password,
_isTransactionUpdating = false,
@ -175,24 +185,12 @@ abstract class ElectrumWalletBase
}
if (seedBytes != null) {
switch (currency) {
case CryptoCurrency.btc:
case CryptoCurrency.ltc:
case CryptoCurrency.tbtc:
return Bip32Slip10Secp256k1.fromSeed(seedBytes);
case CryptoCurrency.bch:
return bitcoinCashHDWallet(seedBytes);
default:
throw Exception("Unsupported currency");
}
return Bip32Slip10Secp256k1.fromSeed(seedBytes);
}
return Bip32Slip10Secp256k1.fromExtendedKey(xpub!, getKeyNetVersion(network));
}
static Bip32Slip10Secp256k1 bitcoinCashHDWallet(List<int> seedBytes) =>
Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/44'/145'/0'") as Bip32Slip10Secp256k1;
int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
inputsCount * 68 + outputsCounts * 34 + 10;
@ -208,7 +206,8 @@ abstract class ElectrumWalletBase
bool? alwaysScan;
bool mempoolAPIEnabled;
final Bip32Slip10Secp256k1 bip32;
final Map<CWBitcoinDerivationType, Bip32Slip10Secp256k1> hdWallets;
Bip32Slip10Secp256k1 get bip32 => walletAddresses.bip32;
final String? _mnemonic;
final EncryptionFileUtils encryptionFileUtils;
@ -1681,11 +1680,17 @@ abstract class ElectrumWalletBase
@action
Future<void> onHistoriesResponse(List<AddressHistoriesResponse> histories) async {
if (histories.isEmpty) {
return;
}
final firstAddress = histories.first;
final isChange = firstAddress.addressRecord.isChange;
final type = firstAddress.addressRecord.type;
final totalAddresses = histories.length;
final totalAddresses = (isChange
? walletAddresses.receiveAddresses.where((element) => element.type == type).length
: walletAddresses.changeAddresses.where((element) => element.type == type).length);
final gapLimit = (isChange
? ElectrumWalletAddressesBase.defaultChangeAddressesCount
: ElectrumWalletAddressesBase.defaultReceiveAddressesCount);
@ -1717,7 +1722,7 @@ abstract class ElectrumWalletBase
// Discover new addresses for the same address type until the gap limit is respected
final newAddresses = await walletAddresses.discoverAddresses(
isChange: isChange,
gap: gapLimit,
derivationType: firstAddress.addressRecord.derivationType,
type: type,
derivationInfo: BitcoinAddressUtils.getDerivationFromType(type),
);

View file

@ -11,6 +11,8 @@ import 'package:mobx/mobx.dart';
part 'electrum_wallet_addresses.g.dart';
enum CWBitcoinDerivationType { old, electrum, bip39, mweb }
class ElectrumWalletAddresses = ElectrumWalletAddressesBase with _$ElectrumWalletAddresses;
const List<BitcoinAddressType> BITCOIN_ADDRESS_TYPES = [
@ -33,7 +35,7 @@ const List<BitcoinAddressType> BITCOIN_CASH_ADDRESS_TYPES = [
abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
ElectrumWalletAddressesBase(
WalletInfo walletInfo, {
required this.bip32,
required this.hdWallets,
required this.network,
required this.isHardwareWallet,
List<BitcoinAddressRecord>? initialAddresses,
@ -98,7 +100,11 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
// TODO: add this variable in `litecoin_wallet_addresses` and just add a cast in cw_bitcoin to use it
final ObservableList<BitcoinAddressRecord> mwebAddresses;
final BasedUtxoNetwork network;
final Bip32Slip10Secp256k1 bip32;
final Map<CWBitcoinDerivationType, Bip32Slip10Secp256k1> hdWallets;
Bip32Slip10Secp256k1 get bip32 =>
hdWallets[CWBitcoinDerivationType.bip39] ?? hdWallets[CWBitcoinDerivationType.electrum]!;
final bool isHardwareWallet;
@observable
@ -331,6 +337,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
final derivationInfo = BitcoinAddressUtils.getDerivationFromType(addressPageType);
final address = BitcoinAddressRecord(
getAddress(
derivationType: CWBitcoinDerivationType.bip39,
isChange: false,
index: newAddressIndex,
addressType: addressPageType,
@ -342,6 +349,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
type: addressPageType,
network: network,
derivationInfo: BitcoinAddressUtils.getDerivationFromType(addressPageType),
derivationType: CWBitcoinDerivationType.bip39,
);
_allAddresses.add(address);
Future.delayed(Duration.zero, () => updateAddressesByMatch());
@ -349,6 +357,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
}
BitcoinBaseAddress generateAddress({
required CWBitcoinDerivationType derivationType,
required bool isChange,
required int index,
required BitcoinAddressType addressType,
@ -358,12 +367,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
}
String getAddress({
required CWBitcoinDerivationType derivationType,
required bool isChange,
required int index,
required BitcoinAddressType addressType,
required BitcoinDerivationInfo derivationInfo,
}) {
return generateAddress(
derivationType: derivationType,
isChange: isChange,
index: index,
addressType: addressType,
@ -372,12 +383,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
}
Future<String> getAddressAsync({
required CWBitcoinDerivationType derivationType,
required bool isChange,
required int index,
required BitcoinAddressType addressType,
required BitcoinDerivationInfo derivationInfo,
}) async =>
getAddress(
derivationType: derivationType,
isChange: isChange,
index: index,
addressType: addressType,
@ -569,12 +582,17 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@action
Future<List<BitcoinAddressRecord>> discoverAddresses({
required CWBitcoinDerivationType derivationType,
required bool isChange,
required int gap,
required BitcoinAddressType type,
required BitcoinDerivationInfo derivationInfo,
}) async {
final gap = (isChange
? ElectrumWalletAddressesBase.defaultChangeAddressesCount
: ElectrumWalletAddressesBase.defaultReceiveAddressesCount);
final newAddresses = await _createNewAddresses(
derivationType: derivationType,
gap,
isChange: isChange,
type: type,
@ -586,36 +604,44 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@action
Future<void> generateInitialAddresses({required BitcoinAddressType type}) async {
// TODO: try all other derivations
final derivationInfo = BitcoinAddressUtils.getDerivationFromType(type);
for (final derivationType in hdWallets.keys) {
final derivationInfo = BitcoinAddressUtils.getDerivationFromType(
type,
isElectrum: derivationType == CWBitcoinDerivationType.electrum,
);
await discoverAddresses(
isChange: false,
gap: defaultReceiveAddressesCount,
type: type,
derivationInfo: derivationInfo,
);
await discoverAddresses(
isChange: true,
gap: defaultChangeAddressesCount,
type: type,
derivationInfo: derivationInfo,
);
await discoverAddresses(
derivationType: derivationType,
isChange: false,
type: type,
derivationInfo: derivationInfo,
);
await discoverAddresses(
derivationType: derivationType,
isChange: true,
type: type,
derivationInfo: derivationInfo,
);
}
}
@action
Future<List<BitcoinAddressRecord>> _createNewAddresses(
int count, {
required CWBitcoinDerivationType derivationType,
required BitcoinDerivationInfo derivationInfo,
bool isChange = false,
BitcoinAddressType? type,
}) async {
final list = <BitcoinAddressRecord>[];
final startIndex = isChange ? totalCountOfChangeAddresses : totalCountOfReceiveAddresses;
final startIndex = (isChange ? receiveAddresses : changeAddresses)
.where((addr) => addr.derivationType == derivationType && addr.type == type)
.length;
for (var i = startIndex; i < count + startIndex; i++) {
final address = BitcoinAddressRecord(
await getAddressAsync(
derivationType: derivationType,
isChange: isChange,
index: i,
addressType: type ?? addressPageType,
@ -623,9 +649,11 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
),
index: i,
isChange: isChange,
isHidden: derivationType == CWBitcoinDerivationType.old,
type: type ?? addressPageType,
network: network,
derivationInfo: derivationInfo,
derivationType: derivationType,
);
list.add(address);
}
@ -646,6 +674,10 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
updateAddressesByMatch();
updateReceiveAddresses();
updateChangeAddresses();
this.hiddenAddresses.addAll(addresses
.where((addressRecord) => addressRecord.isHidden)
.map((addressRecord) => addressRecord.address));
}
@action

View file

@ -98,11 +98,11 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
initialMwebAddresses: initialMwebAddresses,
bip32: bip32,
network: network,
mwebHd: mwebHd,
mwebEnabled: mwebEnabled,
isHardwareWallet: walletInfo.isHardwareWallet,
hdWallets: hdWallets,
);
autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
@ -169,6 +169,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
required bool mempoolAPIEnabled,
}) async {
late Uint8List seedBytes;
late BitcoinDerivationType derivationType;
switch (walletInfo.derivationInfo?.derivationType) {
case DerivationType.bip39:
@ -176,10 +177,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
mnemonic,
passphrase: passphrase ?? "",
);
derivationType = BitcoinDerivationType.bip39;
break;
case DerivationType.electrum:
default:
seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
derivationType = BitcoinDerivationType.electrum;
break;
}
return LitecoinWallet(
@ -246,6 +249,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
walletInfo.derivationInfo!.derivationType ??= snp?.derivationType ?? DerivationType.electrum;
Uint8List? seedBytes = null;
late BitcoinDerivationType derivationType;
final mnemonic = keysData.mnemonic;
final passphrase = keysData.passphrase;
@ -256,10 +260,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
mnemonic,
passphrase: passphrase ?? "",
);
derivationType = BitcoinDerivationType.bip39;
break;
case DerivationType.electrum:
default:
seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
derivationType = BitcoinDerivationType.electrum;
break;
}
}

View file

@ -19,11 +19,11 @@ class LitecoinWalletAddresses = LitecoinWalletAddressesBase with _$LitecoinWalle
abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
LitecoinWalletAddressesBase(
WalletInfo walletInfo, {
required super.bip32,
required super.network,
required super.isHardwareWallet,
required this.mwebHd,
required this.mwebEnabled,
required super.hdWallets,
super.initialAddresses,
super.initialMwebAddresses,
super.initialRegularAddressIndex,
@ -98,13 +98,16 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
List<BitcoinAddressRecord> addressRecords = mwebAddrs
.asMap()
.entries
.map((e) => BitcoinAddressRecord(
e.value,
index: e.key,
type: SegwitAddresType.mweb,
network: network,
derivationInfo: BitcoinAddressUtils.getDerivationFromType(SegwitAddresType.p2wpkh),
))
.map(
(e) => BitcoinAddressRecord(
e.value,
index: e.key,
type: SegwitAddresType.mweb,
network: network,
derivationInfo: BitcoinAddressUtils.getDerivationFromType(SegwitAddresType.p2wpkh),
derivationType: CWBitcoinDerivationType.bip39,
),
)
.toList();
addMwebAddresses(addressRecords);
print("set ${addressRecords.length} mweb addresses");
@ -119,6 +122,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
@override
BitcoinBaseAddress generateAddress({
required CWBitcoinDerivationType derivationType,
required bool isChange,
required int index,
required BitcoinAddressType addressType,
@ -139,6 +143,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
@override
Future<String> getAddressAsync({
required CWBitcoinDerivationType derivationType,
required bool isChange,
required int index,
required BitcoinAddressType addressType,
@ -149,6 +154,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
}
return getAddress(
derivationType: derivationType,
isChange: isChange,
index: index,
addressType: addressType,
@ -208,6 +214,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
type: SegwitAddresType.mweb,
network: network,
derivationInfo: BitcoinAddressUtils.getDerivationFromType(SegwitAddresType.p2wpkh),
derivationType: CWBitcoinDerivationType.bip39,
);
}

View file

@ -6,6 +6,7 @@ import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart';
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/encryption_file_utils.dart';
@ -51,16 +52,17 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
encryptionFileUtils: encryptionFileUtils,
passphrase: passphrase,
mempoolAPIEnabled: mempoolAPIEnabled,
hdWallets: {CWBitcoinDerivationType.bip39: bitcoinCashHDWallet(seedBytes)},
) {
walletAddresses = BitcoinCashWalletAddresses(
walletInfo,
initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
bip32: bip32,
network: network,
initialAddressPageType: addressPageType,
isHardwareWallet: walletInfo.isHardwareWallet,
hdWallets: hdWallets,
);
autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
@ -154,6 +156,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
type: P2pkhAddressType.p2pkh,
network: BitcoinCashNetwork.mainnet,
derivationInfo: BitcoinAddressUtils.getDerivationFromType(P2pkhAddressType.p2pkh),
derivationType: CWBitcoinDerivationType.bip39,
);
} catch (_) {
return BitcoinAddressRecord(
@ -163,6 +166,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
type: P2pkhAddressType.p2pkh,
network: BitcoinCashNetwork.mainnet,
derivationInfo: BitcoinAddressUtils.getDerivationFromType(P2pkhAddressType.p2pkh),
derivationType: CWBitcoinDerivationType.bip39,
);
}
}).toList(),
@ -253,4 +257,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
network: network,
memo: memo,
);
static Bip32Slip10Secp256k1 bitcoinCashHDWallet(List<int> seedBytes) =>
Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/44'/145'/0'") as Bip32Slip10Secp256k1;
}

View file

@ -10,9 +10,9 @@ class BitcoinCashWalletAddresses = BitcoinCashWalletAddressesBase with _$Bitcoin
abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses with Store {
BitcoinCashWalletAddressesBase(
WalletInfo walletInfo, {
required super.bip32,
required super.network,
required super.isHardwareWallet,
required super.hdWallets,
super.initialAddresses,
super.initialRegularAddressIndex,
super.initialChangeAddressIndex,
@ -21,6 +21,7 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi
@override
BitcoinBaseAddress generateAddress({
required CWBitcoinDerivationType derivationType,
required bool isChange,
required int index,
required BitcoinAddressType addressType,

View file

@ -9,6 +9,7 @@ abstract class WalletCredentials {
this.password,
this.passphrase,
this.derivationInfo,
this.derivations,
this.hardwareWalletType,
this.parentAddress,
}) {
@ -25,5 +26,6 @@ abstract class WalletCredentials {
String? passphrase;
WalletInfo? walletInfo;
DerivationInfo? derivationInfo;
List<DerivationInfo>? derivations;
HardwareWalletType? hardwareWalletType;
}

View file

@ -79,6 +79,7 @@ class WalletInfo extends HiveObject {
this.yatLastUsedAddressRaw,
this.showIntroCakePayCard,
this.derivationInfo,
this.derivations,
this.hardwareWalletType,
this.parentAddress,
) : _yatLastUsedAddressController = StreamController<String>.broadcast();
@ -97,6 +98,7 @@ class WalletInfo extends HiveObject {
String yatEid = '',
String yatLastUsedAddressRaw = '',
DerivationInfo? derivationInfo,
List<DerivationInfo>? derivations,
HardwareWalletType? hardwareWalletType,
String? parentAddress,
}) {
@ -114,6 +116,7 @@ class WalletInfo extends HiveObject {
yatLastUsedAddressRaw,
showIntroCakePayCard,
derivationInfo,
derivations,
hardwareWalletType,
parentAddress,
);
@ -196,6 +199,9 @@ class WalletInfo extends HiveObject {
@HiveField(24)
List<String>? manualAddresses;
@HiveField(25)
List<DerivationInfo>? derivations;
String get yatLastUsedAddress => yatLastUsedAddressRaw ?? '';
set yatLastUsedAddress(String address) {

View file

@ -5,6 +5,7 @@ class CWBitcoin extends Bitcoin {
required String name,
required String mnemonic,
required String password,
required List<DerivationInfo>? derivations,
String? passphrase,
}) =>
BitcoinRestoreWalletFromSeedCredentials(
@ -12,6 +13,7 @@ class CWBitcoin extends Bitcoin {
mnemonic: mnemonic,
password: password,
passphrase: passphrase,
derivations: derivations,
);
@override
@ -342,20 +344,12 @@ class CWBitcoin extends Bitcoin {
}
@override
Future<List<DerivationInfo>> getDerivationsFromMnemonic({
Future<List<BitcoinDerivationInfo>> getDerivationsFromMnemonic({
required String mnemonic,
required Node node,
String? passphrase,
}) async {
List<DerivationInfo> list = [];
List<DerivationType> types = await compareDerivationMethods(mnemonic: mnemonic, node: node);
if (types.length == 1 && types.first == DerivationType.electrum) {
return [getElectrumDerivations()[DerivationType.electrum]!.first];
}
final electrumClient = ElectrumClient();
await electrumClient.connectToUri(node.uri, useSSL: node.useSSL);
List<BitcoinDerivationInfo> list = [];
late BasedUtxoNetwork network;
switch (node.type) {
@ -368,77 +362,34 @@ class CWBitcoin extends Bitcoin {
break;
}
for (DerivationType dType in electrum_derivations.keys) {
try {
late List<int> seedBytes;
if (dType == DerivationType.electrum) {
seedBytes = ElectrumV2SeedGenerator.generateFromString(mnemonic, passphrase);
} else if (dType == DerivationType.bip39) {
seedBytes = Bip39SeedGenerator.generateFromString(mnemonic, passphrase);
var electrumSeedBytes;
try {
electrumSeedBytes = ElectrumV2SeedGenerator.generateFromString(mnemonic, passphrase);
} catch (e) {
print("electrum_v2 seed error: $e");
if (passphrase != null && passphrase.isEmpty) {
try {
// TODO: language pick
electrumSeedBytes = ElectrumV1SeedGenerator(mnemonic).generate();
} catch (e) {
print("electrum_v1 seed error: $e");
}
for (DerivationInfo dInfo in electrum_derivations[dType]!) {
try {
DerivationInfo dInfoCopy = DerivationInfo(
derivationType: dInfo.derivationType,
derivationPath: dInfo.derivationPath,
description: dInfo.description,
scriptType: dInfo.scriptType,
);
String balancePath = dInfoCopy.derivationPath!;
int derivationDepth = _countCharOccurrences(balancePath, '/');
// for BIP44
if (derivationDepth == 3 || derivationDepth == 1) {
// we add "/0" so that we generate account 0
balancePath += "/0";
}
final bip32 = Bip32Slip10Secp256k1.fromSeed(seedBytes);
final bip32BalancePath = Bip32PathParser.parse(balancePath);
// derive address at index 0:
final path = bip32BalancePath.addElem(Bip32KeyIndex(0));
String? address;
switch (dInfoCopy.scriptType) {
case "p2wpkh":
address = P2wpkhAddress.fromPath(bip32: bip32, path: path).toAddress(network);
break;
case "p2pkh":
address = P2pkhAddress.fromPath(bip32: bip32, path: path).toAddress(network);
break;
case "p2wpkh-p2sh":
address = P2shAddress.fromPath(bip32: bip32, path: path).toAddress(network);
break;
case "p2tr":
address = P2trAddress.fromPath(bip32: bip32, path: path).toAddress(network);
break;
default:
continue;
}
final sh = BitcoinAddressUtils.scriptHash(address, network: network);
final history = await electrumClient.getHistory(sh);
final balance = await electrumClient.getBalance(sh);
dInfoCopy.balance = balance.entries.firstOrNull?.value.toString() ?? "0";
dInfoCopy.address = address;
dInfoCopy.transactionsCount = history.length;
list.add(dInfoCopy);
} catch (e, s) {
print("derivationInfoError: $e");
print("derivationInfoStack: $s");
}
}
} catch (e) {
print("seed error: $e");
}
}
// sort the list such that derivations with the most transactions are first:
list.sort((a, b) => b.transactionsCount.compareTo(a.transactionsCount));
if (electrumSeedBytes != null) {
list.add(BitcoinDerivationInfos.ELECTRUM);
}
var bip39SeedBytes;
try {
bip39SeedBytes = Bip39SeedGenerator.generateFromString(mnemonic, passphrase);
} catch (_) {}
if (bip39SeedBytes != null) {
list.add(BitcoinDerivationInfos.BIP84);
}
return list;
}

View file

@ -109,6 +109,7 @@ class WalletRestorePage extends BasePage {
// DerivationType derivationType = DerivationType.unknown;
// String? derivationPath = null;
DerivationInfo? derivationInfo;
List<DerivationInfo>? derivations;
@override
Function(BuildContext)? get pushToNextWidget => (context) {
@ -346,6 +347,7 @@ class WalletRestorePage extends BasePage {
}
credentials['derivationInfo'] = this.derivationInfo;
credentials['derivations'] = this.derivations;
credentials['walletType'] = walletRestoreViewModel.type;
return credentials;
}
@ -383,13 +385,13 @@ class WalletRestorePage extends BasePage {
walletRestoreViewModel.state = IsExecutingState();
// get info about the different derivations:
List<DerivationInfo> derivations =
await walletRestoreViewModel.getDerivationInfo(_credentials());
if (walletRestoreViewModel.type == WalletType.nano) {
DerivationInfo? dInfo;
// get info about the different derivations:
List<DerivationInfo> derivations =
await walletRestoreViewModel.getDerivationInfo(_credentials());
int derivationsWithHistory = 0;
int derivationWithHistoryIndex = 0;
for (int i = 0; i < derivations.length; i++) {
@ -416,6 +418,8 @@ class WalletRestorePage extends BasePage {
}
this.derivationInfo = dInfo;
} else {
this.derivations = derivations;
}
await walletRestoreViewModel.create(options: _credentials());

View file

@ -118,6 +118,7 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
mnemonic: restoreWallet.mnemonicSeed ?? '',
password: password,
passphrase: restoreWallet.passphrase,
derivations: [],
);
case WalletType.bitcoinCash:
return bitcoinCash!.createBitcoinCashRestoreWalletFromSeedCredentials(

View file

@ -101,6 +101,7 @@ abstract class WalletCreationVMBase with Store {
address: '',
showIntroCakePayCard: (!walletCreationService.typeExists(type)) && type != WalletType.haven,
derivationInfo: credentials.derivationInfo ?? getDefaultCreateDerivation(),
derivations: credentials.derivations,
hardwareWalletType: credentials.hardwareWalletType,
parentAddress: credentials.parentAddress,
);
@ -200,15 +201,36 @@ abstract class WalletCreationVMBase with Store {
switch (walletType) {
case WalletType.bitcoin:
case WalletType.litecoin:
final derivationList = await bitcoin!.getDerivationsFromMnemonic(
final bitcoinDerivations = await bitcoin!.getDerivationsFromMnemonic(
mnemonic: restoreWallet.mnemonicSeed!,
node: node,
passphrase: restoreWallet.passphrase,
);
if (derivationList.firstOrNull?.transactionsCount == 0 && derivationList.length > 1)
return [];
return derivationList;
List<DerivationInfo> list = [];
for (var derivation in bitcoinDerivations) {
if (derivation.derivationType == DerivationType.electrum) {
list.add(
DerivationInfo(
derivationType: DerivationType.electrum,
derivationPath: "m/0'",
description: "Electrum",
scriptType: "p2wpkh",
),
);
} else {
list.add(
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/84'/0'/0'",
description: "Standard BIP84 native segwit",
scriptType: "p2wpkh",
),
);
}
}
return list;
case WalletType.nano:
return nanoUtil!.getDerivationsFromMnemonic(

View file

@ -91,6 +91,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
final height = options['height'] as int? ?? 0;
name = options['name'] as String;
DerivationInfo? derivationInfo = options["derivationInfo"] as DerivationInfo?;
List<DerivationInfo>? derivations = options["derivations"] as List<DerivationInfo>?;
if (mode == WalletRestoreMode.seed) {
final seed = options['seed'] as String;
@ -105,6 +106,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
mnemonic: seed,
password: password,
passphrase: passphrase,
derivations: derivations,
);
case WalletType.haven:
return haven!.createHavenRestoreWalletFromSeedCredentials(
@ -254,11 +256,36 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
case WalletType.litecoin:
String? mnemonic = credentials['seed'] as String?;
String? passphrase = credentials['passphrase'] as String?;
return bitcoin!.getDerivationsFromMnemonic(
final bitcoinDerivations = await bitcoin!.getDerivationsFromMnemonic(
mnemonic: mnemonic!,
node: node,
passphrase: passphrase,
);
List<DerivationInfo> list = [];
for (var derivation in bitcoinDerivations) {
if (derivation.derivationType.toString().endsWith("electrum")) {
list.add(
DerivationInfo(
derivationType: DerivationType.electrum,
derivationPath: "m/0'",
description: "Electrum",
scriptType: "p2wpkh",
),
);
} else {
list.add(
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/84'/0'/0'",
description: "Standard BIP84 native segwit",
scriptType: "p2wpkh",
),
);
}
}
return list;
case WalletType.nano:
String? mnemonic = credentials['seed'] as String?;
String? seedKey = credentials['private_key'] as String?;

View file

@ -148,6 +148,7 @@ abstract class Bitcoin {
required String name,
required String mnemonic,
required String password,
required List<DerivationInfo>? derivations,
String? passphrase,
});
WalletCredentials createBitcoinRestoreWalletFromWIFCredentials({required String name, required String password, required String wif, WalletInfo? walletInfo});
@ -199,7 +200,7 @@ abstract class Bitcoin {
TransactionPriority getLitecoinTransactionPrioritySlow();
Future<List<DerivationType>> compareDerivationMethods(
{required String mnemonic, required Node node});
Future<List<DerivationInfo>> getDerivationsFromMnemonic(
Future<List<BitcoinDerivationInfo>> getDerivationsFromMnemonic(
{required String mnemonic, required Node node, String? passphrase});
Map<DerivationType, List<DerivationInfo>> getElectrumDerivations();
Future<void> setAddressType(Object wallet, dynamic option);