feat: derivationinfo to address records

This commit is contained in:
Rafael Saes 2024-10-30 12:13:59 -03:00
parent 64caf8479e
commit 433686bce3
16 changed files with 253 additions and 212 deletions

View file

@ -6,7 +6,7 @@ abstract class BaseBitcoinAddressRecord {
BaseBitcoinAddressRecord( BaseBitcoinAddressRecord(
this.address, { this.address, {
required this.index, required this.index,
this.isChange = false, bool isChange = false,
int txCount = 0, int txCount = 0,
int balance = 0, int balance = 0,
String name = '', String name = '',
@ -17,7 +17,8 @@ abstract class BaseBitcoinAddressRecord {
_balance = balance, _balance = balance,
_name = name, _name = name,
_isUsed = isUsed, _isUsed = isUsed,
_isHidden = isHidden ?? isChange; _isHidden = isHidden ?? isChange,
_isChange = isChange;
@override @override
bool operator ==(Object o) => o is BaseBitcoinAddressRecord && address == o.address; bool operator ==(Object o) => o is BaseBitcoinAddressRecord && address == o.address;
@ -25,7 +26,8 @@ abstract class BaseBitcoinAddressRecord {
final String address; final String address;
final bool _isHidden; final bool _isHidden;
bool get isHidden => _isHidden; bool get isHidden => _isHidden;
bool isChange; final bool _isChange;
bool get isChange => _isChange;
final int index; final int index;
int _txCount; int _txCount;
int _balance; int _balance;
@ -55,9 +57,12 @@ abstract class BaseBitcoinAddressRecord {
} }
class BitcoinAddressRecord extends BaseBitcoinAddressRecord { class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
final BitcoinDerivationInfo derivationInfo;
BitcoinAddressRecord( BitcoinAddressRecord(
super.address, { super.address, {
required super.index, required super.index,
required this.derivationInfo,
super.isHidden, super.isHidden,
super.isChange = false, super.isChange = false,
super.txCount = 0, super.txCount = 0,
@ -81,6 +86,9 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
return BitcoinAddressRecord( return BitcoinAddressRecord(
decoded['address'] as String, decoded['address'] as String,
index: decoded['index'] as int, index: decoded['index'] as int,
derivationInfo: BitcoinDerivationInfo.fromJSON(
decoded['derivationInfo'] as Map<String, dynamic>,
),
isHidden: decoded['isHidden'] as bool? ?? false, isHidden: decoded['isHidden'] as bool? ?? false,
isChange: decoded['isChange'] as bool? ?? false, isChange: decoded['isChange'] as bool? ?? false,
isUsed: decoded['isUsed'] as bool? ?? false, isUsed: decoded['isUsed'] as bool? ?? false,
@ -101,6 +109,7 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
String toJSON() => json.encode({ String toJSON() => json.encode({
'address': address, 'address': address,
'index': index, 'index': index,
'derivationInfo': derivationInfo.toJSON(),
'isHidden': isHidden, 'isHidden': isHidden,
'isChange': isChange, 'isChange': isChange,
'isUsed': isUsed, 'isUsed': isUsed,
@ -116,6 +125,8 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
int get labelIndex => index; int get labelIndex => index;
final String? labelHex; final String? labelHex;
static bool isChangeAddress(int labelIndex) => labelIndex == 0;
BitcoinSilentPaymentAddressRecord( BitcoinSilentPaymentAddressRecord(
super.address, { super.address, {
required int labelIndex, required int labelIndex,
@ -126,9 +137,9 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
super.type = SilentPaymentsAddresType.p2sp, super.type = SilentPaymentsAddresType.p2sp,
super.isHidden, super.isHidden,
this.labelHex, this.labelHex,
}) : super(index: labelIndex, isChange: labelIndex == 0) { }) : super(index: labelIndex, isChange: isChangeAddress(labelIndex)) {
if (labelIndex != 1 && labelHex == null) { if (labelIndex != 1 && labelHex == null) {
throw ArgumentError('label must be provided for silent address index > 0'); throw ArgumentError('label must be provided for silent address index != 1');
} }
} }

View file

@ -24,9 +24,14 @@ class BitcoinHardwareWalletService {
final xpub = await bitcoinLedgerApp.getXPubKey(derivationPath: derivationPath); final xpub = await bitcoinLedgerApp.getXPubKey(derivationPath: derivationPath);
final bip32 = Bip32Slip10Secp256k1.fromExtendedKey(xpub).childKey(Bip32KeyIndex(0)); final bip32 = Bip32Slip10Secp256k1.fromExtendedKey(xpub).childKey(Bip32KeyIndex(0));
final fullPath = Bip32PathParser.parse(derivationPath).addElem(Bip32KeyIndex(0));
final address = ECPublic.fromBip32(bip32.derive(fullPath).publicKey)
.toP2wpkhAddress()
.toAddress(BitcoinNetwork.mainnet);
accounts.add(HardwareAccountData( accounts.add(HardwareAccountData(
address: P2wpkhAddress.fromBip32(bip32: bip32, isChange: false, index: i) address: address,
.toAddress(BitcoinNetwork.mainnet),
accountIndex: i, accountIndex: i,
derivationPath: derivationPath, derivationPath: derivationPath,
masterFingerprint: masterFp, masterFingerprint: masterFp,

View file

@ -32,7 +32,10 @@ class BitcoinUnspent extends Unspent {
@override @override
bool operator ==(Object o) { bool operator ==(Object o) {
print('BitcoinUnspent operator =='); if (identical(this, o)) return true;
return o is BitcoinUnspent && hash == o.hash && vout == o.vout; return o is BitcoinUnspent && hash == o.hash && vout == o.vout;
} }
@override
int get hashCode => Object.hash(hash, vout);
} }

View file

@ -360,7 +360,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
updatedUnspentCoins.addAll(await fetchUnspent(address)); updatedUnspentCoins.addAll(await fetchUnspent(address));
})); }));
unspentCoins = updatedUnspentCoins.toSet(); unspentCoins.addAll(updatedUnspentCoins);
if (unspentCoinsInfo.length != updatedUnspentCoins.length) { if (unspentCoinsInfo.length != updatedUnspentCoins.length) {
unspentCoins.forEach((coin) => addCoinInfo(coin)); unspentCoins.forEach((coin) => addCoinInfo(coin));
@ -1033,3 +1033,58 @@ Future<void> delegatedScan(ScanData scanData) async {
// ); // );
// } // }
} }
class ScanNode {
final Uri uri;
final bool? useSSL;
ScanNode(this.uri, this.useSSL);
}
class ScanData {
final SendPort sendPort;
final SilentPaymentOwner silentAddress;
final int height;
final ScanNode? node;
final BasedUtxoNetwork network;
final int chainTip;
final List<String> transactionHistoryIds;
final Map<String, String> labels;
final List<int> labelIndexes;
final bool isSingleScan;
ScanData({
required this.sendPort,
required this.silentAddress,
required this.height,
required this.node,
required this.network,
required this.chainTip,
required this.transactionHistoryIds,
required this.labels,
required this.labelIndexes,
required this.isSingleScan,
});
factory ScanData.fromHeight(ScanData scanData, int newHeight) {
return ScanData(
sendPort: scanData.sendPort,
silentAddress: scanData.silentAddress,
height: newHeight,
node: scanData.node,
network: scanData.network,
chainTip: scanData.chainTip,
transactionHistoryIds: scanData.transactionHistoryIds,
labels: scanData.labels,
labelIndexes: scanData.labelIndexes,
isSingleScan: scanData.isSingleScan,
);
}
}
class SyncResponse {
final int height;
final SyncStatus syncStatus;
SyncResponse(this.height, this.syncStatus);
}

View file

@ -39,27 +39,44 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
required bool isChange, required bool isChange,
required int index, required int index,
required BitcoinAddressType addressType, required BitcoinAddressType addressType,
required BitcoinDerivationInfo derivationInfo,
}) { }) {
switch (addressType) { switch (addressType) {
case P2pkhAddressType.p2pkh: case P2pkhAddressType.p2pkh:
return P2pkhAddress.fromBip32(bip32: bip32, isChange: isChange, index: index); return P2pkhAddress.fromDerivation(
case SegwitAddresType.p2tr:
return P2trAddress.fromBip32(bip32: bip32, isChange: isChange, index: index);
case SegwitAddresType.p2wsh:
return P2wshAddress.fromBip32(bip32: bip32, isChange: isChange, index: index);
case P2shAddressType.p2wpkhInP2sh:
return P2shAddress.fromBip32(
bip32: bip32, bip32: bip32,
derivationInfo: derivationInfo,
isChange: isChange,
index: index,
);
case SegwitAddresType.p2tr:
return P2trAddress.fromDerivation(
bip32: bip32,
derivationInfo: derivationInfo,
isChange: isChange,
index: index,
);
case SegwitAddresType.p2wsh:
return P2wshAddress.fromDerivation(
bip32: bip32,
derivationInfo: derivationInfo,
isChange: isChange,
index: index,
);
case P2shAddressType.p2wpkhInP2sh:
return P2shAddress.fromDerivation(
bip32: bip32,
derivationInfo: derivationInfo,
isChange: isChange, isChange: isChange,
index: index, index: index,
type: P2shAddressType.p2wpkhInP2sh, type: P2shAddressType.p2wpkhInP2sh,
); );
case SegwitAddresType.p2wpkh: case SegwitAddresType.p2wpkh:
return P2wpkhAddress.fromBip32( return P2wpkhAddress.fromDerivation(
bip32: bip32, bip32: bip32,
derivationInfo: derivationInfo,
isChange: isChange, isChange: isChange,
index: index, index: index,
isElectrum: false, // TODO:
); );
default: default:
throw ArgumentError('Invalid address type'); throw ArgumentError('Invalid address type');

View file

@ -4,11 +4,8 @@ import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_bitcoin/electrum_transaction_info.dart'; import 'package:cw_bitcoin/electrum_transaction_info.dart';
import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/transaction_history.dart'; import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/utils/file.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_bitcoin/electrum_transaction_info.dart';
part 'electrum_transaction_history.g.dart'; part 'electrum_transaction_history.g.dart';

View file

@ -1,7 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:isolate';
import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@ -38,7 +37,6 @@ import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:hex/hex.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
part 'electrum_wallet.g.dart'; part 'electrum_wallet.g.dart';
@ -69,7 +67,8 @@ abstract class ElectrumWalletBase
_password = password, _password = password,
_isTransactionUpdating = false, _isTransactionUpdating = false,
isEnabledAutoGenerateSubaddress = true, isEnabledAutoGenerateSubaddress = true,
unspentCoins = {}, // TODO: inital unspent coins
unspentCoins = ObservableSet(),
scripthashesListening = {}, scripthashesListening = {},
balance = ObservableMap<CryptoCurrency, ElectrumBalance>.of(currency != null balance = ObservableMap<CryptoCurrency, ElectrumBalance>.of(currency != null
? { ? {
@ -221,8 +220,7 @@ abstract class ElectrumWalletBase
); );
String _password; String _password;
@observable ObservableSet<BitcoinUnspent> unspentCoins;
Set<BitcoinUnspent> unspentCoins;
@observable @observable
TransactionPriorities? feeRates; TransactionPriorities? feeRates;
@ -404,7 +402,7 @@ abstract class ElectrumWalletBase
bool _isBelowDust(int amount) => amount <= _dustAmount && network != BitcoinNetwork.testnet; bool _isBelowDust(int amount) => amount <= _dustAmount && network != BitcoinNetwork.testnet;
UtxoDetails _createUTXOS({ TxCreateUtxoDetails _createUTXOS({
required bool sendAll, required bool sendAll,
required int credentialsAmount, required int credentialsAmount,
required bool paysToSilentPayment, required bool paysToSilentPayment,
@ -484,13 +482,13 @@ abstract class ElectrumWalletBase
.toHex(); .toHex();
} }
// TODO: isElectrum if (utx.bitcoinAddressRecord is BitcoinAddressRecord) {
final derivationPath = BitcoinAddressUtils.getDerivationPath( final derivationPath = (utx.bitcoinAddressRecord as BitcoinAddressRecord)
type: utx.bitcoinAddressRecord.type, .derivationInfo
account: utx.bitcoinAddressRecord.isChange ? 1 : 0, .derivationPath
index: utx.bitcoinAddressRecord.index, .toString();
);
publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath); publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath);
}
utxos.add( utxos.add(
UtxoWithAddress( UtxoWithAddress(
@ -521,7 +519,7 @@ abstract class ElectrumWalletBase
throw BitcoinTransactionNoInputsException(); throw BitcoinTransactionNoInputsException();
} }
return UtxoDetails( return TxCreateUtxoDetails(
availableInputs: availableInputs, availableInputs: availableInputs,
unconfirmedCoins: unconfirmedCoins, unconfirmedCoins: unconfirmedCoins,
utxos: utxos, utxos: utxos,
@ -662,11 +660,7 @@ abstract class ElectrumWalletBase
isChange: true, isChange: true,
)); ));
final changeDerivationPath = BitcoinAddressUtils.getDerivationPath( final changeDerivationPath = changeAddress.derivationInfo.derivationPath.toString();
type: changeAddress.type,
account: changeAddress.isChange ? 1 : 0,
index: changeAddress.index,
);
utxoDetails.publicKeys[address.pubKeyHash()] = utxoDetails.publicKeys[address.pubKeyHash()] =
PublicKeyWithDerivationPath('', changeDerivationPath); PublicKeyWithDerivationPath('', changeDerivationPath);
@ -1176,7 +1170,7 @@ abstract class ElectrumWalletBase
updatedUnspentCoins.addAll(await fetchUnspent(address)); updatedUnspentCoins.addAll(await fetchUnspent(address));
})); }));
unspentCoins = updatedUnspentCoins.toSet(); unspentCoins.addAll(updatedUnspentCoins);
if (unspentCoinsInfo.length != updatedUnspentCoins.length) { if (unspentCoinsInfo.length != updatedUnspentCoins.length) {
unspentCoins.forEach((coin) => addCoinInfo(coin)); unspentCoins.forEach((coin) => addCoinInfo(coin));
@ -1220,17 +1214,7 @@ abstract class ElectrumWalletBase
final newUnspentCoins = (await fetchUnspent(addressRecord)).toSet(); final newUnspentCoins = (await fetchUnspent(addressRecord)).toSet();
await updateCoins(newUnspentCoins); await updateCoins(newUnspentCoins);
print([1, unspentCoins.containsAll(newUnspentCoins)]); unspentCoins.addAll(newUnspentCoins);
if (!unspentCoins.containsAll(newUnspentCoins)) {
newUnspentCoins.forEach((coin) {
print(unspentCoins.contains(coin));
print([coin.vout, coin.hash]);
print([unspentCoins.first.vout, unspentCoins.first.hash]);
if (!unspentCoins.contains(coin)) {
unspentCoins.add(coin);
}
});
}
// if (unspentCoinsInfo.length != unspentCoins.length) { // if (unspentCoinsInfo.length != unspentCoins.length) {
// unspentCoins.forEach(addCoinInfo); // unspentCoins.forEach(addCoinInfo);
@ -1400,7 +1384,7 @@ abstract class ElectrumWalletBase
if (index + 1 <= script.length) { if (index + 1 <= script.length) {
try { try {
final opReturnData = script[index + 1].toString(); final opReturnData = script[index + 1].toString();
memo = utf8.decode(HEX.decode(opReturnData)); memo = StringUtils.decode(BytesUtils.fromHexString(opReturnData));
continue; continue;
} catch (_) { } catch (_) {
throw Exception('Cannot decode OP_RETURN data'); throw Exception('Cannot decode OP_RETURN data');
@ -1708,6 +1692,7 @@ abstract class ElectrumWalletBase
isChange: addressRecord.isChange, isChange: addressRecord.isChange,
gap: gapLimit, gap: gapLimit,
type: addressRecord.type, type: addressRecord.type,
derivationInfo: BitcoinAddressUtils.getDerivationFromType(addressRecord.type),
); );
} }
} }
@ -1805,21 +1790,17 @@ abstract class ElectrumWalletBase
@override @override
Future<String> signMessage(String message, {String? address = null}) async { Future<String> signMessage(String message, {String? address = null}) async {
Bip32Slip10Secp256k1 HD = bip32; final record = walletAddresses.getFromAddresses(address!);
final record = walletAddresses.allAddresses.firstWhere((element) => element.address == address); final path = Bip32PathParser.parse(walletInfo.derivationInfo!.derivationPath!)
.addElem(
Bip32KeyIndex(BitcoinAddressUtils.getAccountFromChange(record.isChange)),
)
.addElem(Bip32KeyIndex(record.index));
if (record.isChange) { final priv = ECPrivate.fromHex(bip32.derive(path).privateKey.toHex());
HD = HD.childKey(Bip32KeyIndex(1));
} else {
HD = HD.childKey(Bip32KeyIndex(0));
}
HD = HD.childKey(Bip32KeyIndex(record.index)); final hexEncoded = priv.signMessage(StringUtils.encode(message));
final priv = ECPrivate.fromHex(HD.privateKey.privKey.toHex());
String messagePrefix = '\x18Bitcoin Signed Message:\n';
final hexEncoded = priv.signMessage(utf8.encode(message), messagePrefix: messagePrefix);
final decodedSig = hex.decode(hexEncoded); final decodedSig = hex.decode(hexEncoded);
return base64Encode(decodedSig); return base64Encode(decodedSig);
} }
@ -1835,7 +1816,7 @@ abstract class ElectrumWalletBase
if (signature.endsWith('=')) { if (signature.endsWith('=')) {
sigDecodedBytes = base64.decode(signature); sigDecodedBytes = base64.decode(signature);
} else { } else {
sigDecodedBytes = hex.decode(signature); sigDecodedBytes = BytesUtils.fromHexString(signature);
} }
if (sigDecodedBytes.length != 64 && sigDecodedBytes.length != 65) { if (sigDecodedBytes.length != 64 && sigDecodedBytes.length != 65) {
@ -1845,7 +1826,7 @@ abstract class ElectrumWalletBase
String messagePrefix = '\x18Bitcoin Signed Message:\n'; String messagePrefix = '\x18Bitcoin Signed Message:\n';
final messageHash = QuickCrypto.sha256Hash( final messageHash = QuickCrypto.sha256Hash(
BitcoinSignerUtils.magicMessage(utf8.encode(message), messagePrefix)); BitcoinSignerUtils.magicMessage(StringUtils.encode(message), messagePrefix));
List<int> correctSignature = List<int> correctSignature =
sigDecodedBytes.length == 65 ? sigDecodedBytes.sublist(1) : List.from(sigDecodedBytes); sigDecodedBytes.length == 65 ? sigDecodedBytes.sublist(1) : List.from(sigDecodedBytes);
@ -1911,14 +1892,14 @@ abstract class ElectrumWalletBase
break; break;
case ConnectionStatus.disconnected: case ConnectionStatus.disconnected:
// if (syncStatus is! NotConnectedSyncStatus) { if (syncStatus is! NotConnectedSyncStatus) {
// syncStatus = NotConnectedSyncStatus(); syncStatus = NotConnectedSyncStatus();
// } }
break; break;
case ConnectionStatus.failed: case ConnectionStatus.failed:
// if (syncStatus is! LostConnectionSyncStatus) { if (syncStatus is! LostConnectionSyncStatus) {
// syncStatus = LostConnectionSyncStatus(); syncStatus = LostConnectionSyncStatus();
// } }
break; break;
case ConnectionStatus.connecting: case ConnectionStatus.connecting:
if (syncStatus is! ConnectingSyncStatus) { if (syncStatus is! ConnectingSyncStatus) {
@ -1989,7 +1970,7 @@ abstract class ElectrumWalletBase
if (index + 1 <= script.length) { if (index + 1 <= script.length) {
try { try {
final opReturnData = script[index + 1].toString(); final opReturnData = script[index + 1].toString();
final decodedString = utf8.decode(HEX.decode(opReturnData)); final decodedString = StringUtils.decode(BytesUtils.fromHexString(opReturnData));
outputAddresses.add('OP_RETURN:$decodedString'); outputAddresses.add('OP_RETURN:$decodedString');
} catch (_) { } catch (_) {
outputAddresses.add('OP_RETURN:'); outputAddresses.add('OP_RETURN:');
@ -2005,61 +1986,6 @@ abstract class ElectrumWalletBase
} }
} }
class ScanNode {
final Uri uri;
final bool? useSSL;
ScanNode(this.uri, this.useSSL);
}
class ScanData {
final SendPort sendPort;
final SilentPaymentOwner silentAddress;
final int height;
final ScanNode? node;
final BasedUtxoNetwork network;
final int chainTip;
final List<String> transactionHistoryIds;
final Map<String, String> labels;
final List<int> labelIndexes;
final bool isSingleScan;
ScanData({
required this.sendPort,
required this.silentAddress,
required this.height,
required this.node,
required this.network,
required this.chainTip,
required this.transactionHistoryIds,
required this.labels,
required this.labelIndexes,
required this.isSingleScan,
});
factory ScanData.fromHeight(ScanData scanData, int newHeight) {
return ScanData(
sendPort: scanData.sendPort,
silentAddress: scanData.silentAddress,
height: newHeight,
node: scanData.node,
network: scanData.network,
chainTip: scanData.chainTip,
transactionHistoryIds: scanData.transactionHistoryIds,
labels: scanData.labels,
labelIndexes: scanData.labelIndexes,
isSingleScan: scanData.isSingleScan,
);
}
}
class SyncResponse {
final int height;
final SyncStatus syncStatus;
SyncResponse(this.height, this.syncStatus);
}
class EstimatedTxResult { class EstimatedTxResult {
EstimatedTxResult({ EstimatedTxResult({
required this.utxos, required this.utxos,
@ -2095,7 +2021,7 @@ class PublicKeyWithDerivationPath {
final String publicKey; final String publicKey;
} }
class UtxoDetails { class TxCreateUtxoDetails {
final List<BitcoinUnspent> availableInputs; final List<BitcoinUnspent> availableInputs;
final List<BitcoinUnspent> unconfirmedCoins; final List<BitcoinUnspent> unconfirmedCoins;
final List<UtxoWithAddress> utxos; final List<UtxoWithAddress> utxos;
@ -2106,7 +2032,7 @@ class UtxoDetails {
final bool spendsSilentPayment; final bool spendsSilentPayment;
final bool spendsUnconfirmedTX; final bool spendsUnconfirmedTX;
UtxoDetails({ TxCreateUtxoDetails({
required this.availableInputs, required this.availableInputs,
required this.unconfirmedCoins, required this.unconfirmedCoins,
required this.utxos, required this.utxos,

View file

@ -43,7 +43,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
int initialSilentAddressIndex = 0, int initialSilentAddressIndex = 0,
List<BitcoinAddressRecord>? initialMwebAddresses, List<BitcoinAddressRecord>? initialMwebAddresses,
BitcoinAddressType? initialAddressPageType, BitcoinAddressType? initialAddressPageType,
}) : _allAddresses = (initialAddresses ?? []).toSet(), }) : _allAddresses = ObservableSet.of(initialAddresses ?? []),
addressesByReceiveType = addressesByReceiveType =
ObservableList<BaseBitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()), ObservableList<BaseBitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()),
receiveAddresses = ObservableList<BitcoinAddressRecord>.of( receiveAddresses = ObservableList<BitcoinAddressRecord>.of(
@ -63,11 +63,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
mwebAddresses = mwebAddresses =
ObservableList<BitcoinAddressRecord>.of((initialMwebAddresses ?? []).toSet()), ObservableList<BitcoinAddressRecord>.of((initialMwebAddresses ?? []).toSet()),
super(walletInfo) { super(walletInfo) {
silentAddress = SilentPaymentOwner.fromPrivateKeys( // TODO: initial silent address, not every time
b_scan: ECPrivate.fromHex(bip32.derive(SCAN_PATH).privateKey.toHex()), silentAddress = SilentPaymentOwner.fromBip32(bip32);
b_spend: ECPrivate.fromHex(bip32.derive(SPEND_PATH).privateKey.toHex()),
network: network,
);
if (silentAddresses.length == 0) { if (silentAddresses.length == 0) {
silentAddresses.add(BitcoinSilentPaymentAddressRecord( silentAddresses.add(BitcoinSilentPaymentAddressRecord(
silentAddress.toString(), silentAddress.toString(),
@ -91,8 +89,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
static const defaultChangeAddressesCount = 17; static const defaultChangeAddressesCount = 17;
static const gap = 20; static const gap = 20;
@observable final ObservableSet<BitcoinAddressRecord> _allAddresses;
final Set<BitcoinAddressRecord> _allAddresses;
final ObservableList<BaseBitcoinAddressRecord> addressesByReceiveType; final ObservableList<BaseBitcoinAddressRecord> addressesByReceiveType;
final ObservableList<BitcoinAddressRecord> receiveAddresses; final ObservableList<BitcoinAddressRecord> receiveAddresses;
final ObservableList<BitcoinAddressRecord> changeAddresses; final ObservableList<BitcoinAddressRecord> changeAddresses;
@ -119,6 +116,10 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@computed @computed
List<BitcoinAddressRecord> get allAddresses => _allAddresses.toList(); List<BitcoinAddressRecord> get allAddresses => _allAddresses.toList();
BitcoinAddressRecord getFromAddresses(String address) {
return _allAddresses.firstWhere((element) => element.address == address);
}
@override @override
@computed @computed
String get address { String get address {
@ -189,7 +190,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
} }
@override @override
String get primaryAddress => getAddress(isChange: false, index: 0, addressType: addressPageType); String get primaryAddress => _allAddresses.first.address;
Map<String, int> currentReceiveAddressIndexByType; Map<String, int> currentReceiveAddressIndexByType;
@ -250,7 +251,6 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
updateAddressesByMatch(); updateAddressesByMatch();
updateReceiveAddresses(); updateReceiveAddresses();
updateChangeAddresses(); updateChangeAddresses();
_validateAddresses();
await updateAddressesInBox(); await updateAddressesInBox();
if (currentReceiveAddressIndex >= receiveAddresses.length) { if (currentReceiveAddressIndex >= receiveAddresses.length) {
@ -263,15 +263,13 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
} }
@action @action
Future<BitcoinAddressRecord> getChangeAddress( Future<BitcoinAddressRecord> getChangeAddress({
{List<BitcoinUnspent>? inputs, List<BitcoinOutput>? outputs, bool isPegIn = false}) async { List<BitcoinUnspent>? inputs,
List<BitcoinOutput>? outputs,
bool isPegIn = false,
}) async {
updateChangeAddresses(); updateChangeAddresses();
if (changeAddresses.isEmpty) {
final newAddresses = await _createNewAddresses(gap, isChange: true);
addAddresses(newAddresses);
}
if (currentChangeAddressIndex >= changeAddresses.length) { if (currentChangeAddressIndex >= changeAddresses.length) {
currentChangeAddressIndex = 0; currentChangeAddressIndex = 0;
} }
@ -326,13 +324,20 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
final newAddressIndex = addressesByReceiveType.fold( final newAddressIndex = addressesByReceiveType.fold(
0, (int acc, addressRecord) => addressRecord.isChange == false ? acc + 1 : acc); 0, (int acc, addressRecord) => addressRecord.isChange == false ? acc + 1 : acc);
final derivationInfo = BitcoinAddressUtils.getDerivationFromType(addressPageType);
final address = BitcoinAddressRecord( final address = BitcoinAddressRecord(
getAddress(isChange: false, index: newAddressIndex, addressType: addressPageType), getAddress(
isChange: false,
index: newAddressIndex,
addressType: addressPageType,
derivationInfo: derivationInfo,
),
index: newAddressIndex, index: newAddressIndex,
isChange: false, isChange: false,
name: label, name: label,
type: addressPageType, type: addressPageType,
network: network, network: network,
derivationInfo: BitcoinAddressUtils.getDerivationFromType(addressPageType),
); );
_allAddresses.add(address); _allAddresses.add(address);
Future.delayed(Duration.zero, () => updateAddressesByMatch()); Future.delayed(Duration.zero, () => updateAddressesByMatch());
@ -343,6 +348,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
required bool isChange, required bool isChange,
required int index, required int index,
required BitcoinAddressType addressType, required BitcoinAddressType addressType,
required BitcoinDerivationInfo derivationInfo,
}) { }) {
throw UnimplementedError(); throw UnimplementedError();
} }
@ -351,17 +357,28 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
required bool isChange, required bool isChange,
required int index, required int index,
required BitcoinAddressType addressType, required BitcoinAddressType addressType,
required BitcoinDerivationInfo derivationInfo,
}) { }) {
return generateAddress(isChange: isChange, index: index, addressType: addressType) return generateAddress(
.toAddress(network); isChange: isChange,
index: index,
addressType: addressType,
derivationInfo: derivationInfo,
).toAddress(network);
} }
Future<String> getAddressAsync({ Future<String> getAddressAsync({
required bool isChange, required bool isChange,
required int index, required int index,
required BitcoinAddressType addressType, required BitcoinAddressType addressType,
required BitcoinDerivationInfo derivationInfo,
}) async => }) async =>
getAddress(isChange: isChange, index: index, addressType: addressType); getAddress(
isChange: isChange,
index: index,
addressType: addressType,
derivationInfo: derivationInfo,
);
@action @action
void addBitcoinAddressTypes() { void addBitcoinAddressTypes() {
@ -551,23 +568,41 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
required bool isChange, required bool isChange,
required int gap, required int gap,
required BitcoinAddressType type, required BitcoinAddressType type,
required BitcoinDerivationInfo derivationInfo,
}) async { }) async {
print("_allAddresses: ${_allAddresses.length}"); final newAddresses = await _createNewAddresses(
final newAddresses = await _createNewAddresses(gap, isChange: isChange, type: type); gap,
isChange: isChange,
type: type,
derivationInfo: derivationInfo,
);
addAddresses(newAddresses); addAddresses(newAddresses);
print("_allAddresses: ${_allAddresses.length}");
return newAddresses; return newAddresses;
} }
@action @action
Future<void> generateInitialAddresses({required BitcoinAddressType type}) async { Future<void> generateInitialAddresses({required BitcoinAddressType type}) async {
await discoverAddresses(isChange: false, gap: defaultReceiveAddressesCount, type: type); // TODO: try all other derivations
await discoverAddresses(isChange: true, gap: defaultChangeAddressesCount, type: type); final derivationInfo = BitcoinAddressUtils.getDerivationFromType(type);
await discoverAddresses(
isChange: false,
gap: defaultReceiveAddressesCount,
type: type,
derivationInfo: derivationInfo,
);
await discoverAddresses(
isChange: true,
gap: defaultChangeAddressesCount,
type: type,
derivationInfo: derivationInfo,
);
} }
@action @action
Future<List<BitcoinAddressRecord>> _createNewAddresses( Future<List<BitcoinAddressRecord>> _createNewAddresses(
int count, { int count, {
required BitcoinDerivationInfo derivationInfo,
bool isChange = false, bool isChange = false,
BitcoinAddressType? type, BitcoinAddressType? type,
}) async { }) async {
@ -580,11 +615,13 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
isChange: isChange, isChange: isChange,
index: i, index: i,
addressType: type ?? addressPageType, addressType: type ?? addressPageType,
derivationInfo: derivationInfo,
), ),
index: i, index: i,
isChange: isChange, isChange: isChange,
type: type ?? addressPageType, type: type ?? addressPageType,
network: network, network: network,
derivationInfo: derivationInfo,
); );
list.add(address); list.add(address);
} }
@ -618,32 +655,6 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
updateAddressesByMatch(); updateAddressesByMatch();
} }
void _validateAddresses() {
_allAddresses.forEach((element) async {
if (element.type == SegwitAddresType.mweb) {
// this would add a ton of startup lag for mweb addresses since we have 1000 of them
return;
}
if (!element.isChange &&
element.address !=
await getAddressAsync(
isChange: false,
index: element.index,
addressType: element.type,
)) {
element.isChange = true;
} else if (element.isChange &&
element.address !=
await getAddressAsync(
isChange: true,
index: element.index,
addressType: element.type,
)) {
element.isChange = false;
}
});
}
@action @action
Future<void> setAddressType(BitcoinAddressType type) async { Future<void> setAddressType(BitcoinAddressType type) async {
_addressPageType = type; _addressPageType = type;

View file

@ -3,7 +3,6 @@ import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_balance.dart'; import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_bitcoin/electrum_derivations.dart';
import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
@ -92,7 +91,7 @@ class ElectrumWalletSnapshot {
final derivationType = DerivationType final derivationType = DerivationType
.values[(data['derivationTypeIndex'] as int?) ?? DerivationType.electrum.index]; .values[(data['derivationTypeIndex'] as int?) ?? DerivationType.electrum.index];
final derivationPath = data['derivationPath'] as String? ?? electrum_path; final derivationPath = data['derivationPath'] as String? ?? ELECTRUM_PATH;
try { try {
regularAddressIndexByType = { regularAddressIndexByType = {

View file

@ -28,7 +28,12 @@ class LitecoinHardwareWalletService {
final bip32 = final bip32 =
Bip32Slip10Secp256k1.fromExtendedKey(xpub, xpubVersion).childKey(Bip32KeyIndex(0)); Bip32Slip10Secp256k1.fromExtendedKey(xpub, xpubVersion).childKey(Bip32KeyIndex(0));
final address = P2wpkhAddress.fromBip32(bip32: bip32, isChange: false, index: 0); final address = P2wpkhAddress.fromDerivation(
bip32: bip32,
derivationInfo: BitcoinDerivationInfos.LITECOIN,
isChange: false,
index: 0,
);
accounts.add(HardwareAccountData( accounts.add(HardwareAccountData(
address: address.toAddress(LitecoinNetwork.mainnet), address: address.toAddress(LitecoinNetwork.mainnet),

View file

@ -21,7 +21,6 @@ import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
import 'package:cw_bitcoin/bitcoin_unspent.dart'; import 'package:cw_bitcoin/bitcoin_unspent.dart';
import 'package:cw_bitcoin/electrum_transaction_info.dart'; import 'package:cw_bitcoin/electrum_transaction_info.dart';
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
import 'package:cw_bitcoin/electrum_derivations.dart';
import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/pending_transaction.dart';
@ -243,7 +242,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
walletInfo.derivationInfo ??= DerivationInfo(); walletInfo.derivationInfo ??= DerivationInfo();
// set the default if not present: // set the default if not present:
walletInfo.derivationInfo!.derivationPath ??= snp?.derivationPath ?? electrum_path; walletInfo.derivationInfo!.derivationPath ??= snp?.derivationPath ?? ELECTRUM_PATH;
walletInfo.derivationInfo!.derivationType ??= snp?.derivationType ?? DerivationType.electrum; walletInfo.derivationInfo!.derivationType ??= snp?.derivationType ?? DerivationType.electrum;
Uint8List? seedBytes = null; Uint8List? seedBytes = null;
@ -435,13 +434,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
@action @action
@override @override
Future<void> rescan({ Future<void> rescan({required int height}) async {
required int height,
int? chainTip,
ScanData? scanData,
bool? doSingleScan,
bool? usingElectrs,
}) async {
_syncTimer?.cancel(); _syncTimer?.cancel();
await walletInfo.updateRestoreHeight(height); await walletInfo.updateRestoreHeight(height);

View file

@ -103,6 +103,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
index: e.key, index: e.key,
type: SegwitAddresType.mweb, type: SegwitAddresType.mweb,
network: network, network: network,
derivationInfo: BitcoinAddressUtils.getDerivationFromType(SegwitAddresType.p2wpkh),
)) ))
.toList(); .toList();
addMwebAddresses(addressRecords); addMwebAddresses(addressRecords);
@ -121,12 +122,18 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
required bool isChange, required bool isChange,
required int index, required int index,
required BitcoinAddressType addressType, required BitcoinAddressType addressType,
required BitcoinDerivationInfo derivationInfo,
}) { }) {
if (addressType == SegwitAddresType.mweb) { if (addressType == SegwitAddresType.mweb) {
return MwebAddress.fromAddress(address: mwebAddrs[0], network: network); return MwebAddress.fromAddress(address: mwebAddrs[0], network: network);
} }
return P2wpkhAddress.fromBip32(bip32: bip32, isChange: isChange, index: index); return P2wpkhAddress.fromDerivation(
bip32: bip32,
derivationInfo: derivationInfo,
isChange: isChange,
index: index,
);
} }
} }
@ -135,12 +142,18 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
required bool isChange, required bool isChange,
required int index, required int index,
required BitcoinAddressType addressType, required BitcoinAddressType addressType,
required BitcoinDerivationInfo derivationInfo,
}) async { }) async {
if (addressType == SegwitAddresType.mweb) { if (addressType == SegwitAddresType.mweb) {
await ensureMwebAddressUpToIndexExists(index); await ensureMwebAddressUpToIndexExists(index);
} }
return getAddress(isChange: isChange, index: index, addressType: addressType); return getAddress(
isChange: isChange,
index: index,
addressType: addressType,
derivationInfo: derivationInfo,
);
} }
@action @action
@ -194,6 +207,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
index: 0, index: 0,
type: SegwitAddresType.mweb, type: SegwitAddresType.mweb,
network: network, network: network,
derivationInfo: BitcoinAddressUtils.getDerivationFromType(SegwitAddresType.p2wpkh),
); );
} }

View file

@ -411,10 +411,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: google_identity_services_web name: google_identity_services_web
sha256: "5be191523702ba8d7a01ca97c17fca096822ccf246b0a9f11923a6ded06199b6" sha256: "0c56c2c5d60d6dfaf9725f5ad4699f04749fb196ee5a70487a46ef184837ccf6"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.1+4" version: "0.3.0+2"
googleapis_auth: googleapis_auth:
dependency: transitive dependency: transitive
description: description:
@ -467,10 +467,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: http name: http
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.2" version: "1.2.0"
http2: http2:
dependency: transitive dependency: transitive
description: description:
@ -845,10 +845,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_web name: shared_preferences_web
sha256: "59dc807b94d29d52ddbb1b3c0d3b9d0a67fc535a64e62a5542c8db0513fcb6c2" sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.1" version: "2.2.2"
shared_preferences_windows: shared_preferences_windows:
dependency: transitive dependency: transitive
description: description:
@ -1041,18 +1041,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: web name: web
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.1" version: "0.4.2"
web_socket_channel: web_socket_channel:
dependency: transitive dependency: transitive
description: description:
name: web_socket_channel name: web_socket_channel
sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" sha256: "939ab60734a4f8fa95feacb55804fa278de28bdeef38e616dc08e44a84adea23"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.5" version: "2.4.3"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:

View file

@ -153,6 +153,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
isChange: addr.isChange, isChange: addr.isChange,
type: P2pkhAddressType.p2pkh, type: P2pkhAddressType.p2pkh,
network: BitcoinCashNetwork.mainnet, network: BitcoinCashNetwork.mainnet,
derivationInfo: BitcoinAddressUtils.getDerivationFromType(P2pkhAddressType.p2pkh),
); );
} catch (_) { } catch (_) {
return BitcoinAddressRecord( return BitcoinAddressRecord(
@ -161,6 +162,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
isChange: addr.isChange, isChange: addr.isChange,
type: P2pkhAddressType.p2pkh, type: P2pkhAddressType.p2pkh,
network: BitcoinCashNetwork.mainnet, network: BitcoinCashNetwork.mainnet,
derivationInfo: BitcoinAddressUtils.getDerivationFromType(P2pkhAddressType.p2pkh),
); );
} }
}).toList(), }).toList(),

View file

@ -24,6 +24,12 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi
required bool isChange, required bool isChange,
required int index, required int index,
required BitcoinAddressType addressType, required BitcoinAddressType addressType,
required BitcoinDerivationInfo derivationInfo,
}) => }) =>
P2pkhAddress.fromBip32(bip32: bip32, isChange: isChange, index: index); P2pkhAddress.fromDerivation(
bip32: bip32,
derivationInfo: derivationInfo,
isChange: isChange,
index: index,
);
} }

View file

@ -196,9 +196,6 @@ class WalletInfo extends HiveObject {
@HiveField(24) @HiveField(24)
List<String>? manualAddresses; List<String>? manualAddresses;
String get yatLastUsedAddress => yatLastUsedAddressRaw ?? ''; String get yatLastUsedAddress => yatLastUsedAddressRaw ?? '';
set yatLastUsedAddress(String address) { set yatLastUsedAddress(String address) {