mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-23 12:09:43 +00:00
380 lines
12 KiB
Dart
380 lines
12 KiB
Dart
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/electrum_wallet_addresses.dart';
|
|
import 'package:cw_core/wallet_info.dart';
|
|
import 'package:mobx/mobx.dart';
|
|
|
|
part 'bitcoin_wallet_addresses.g.dart';
|
|
|
|
class BitcoinWalletAddresses = BitcoinWalletAddressesBase with _$BitcoinWalletAddresses;
|
|
|
|
abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
|
|
BitcoinWalletAddressesBase(
|
|
WalletInfo walletInfo, {
|
|
required super.network,
|
|
required super.isHardwareWallet,
|
|
required super.hdWallets,
|
|
super.initialAddresses,
|
|
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
|
|
List<BitcoinReceivedSPAddressRecord>? initialReceivedSPAddresses,
|
|
}) : silentPaymentAddresses = ObservableList<BitcoinSilentPaymentAddressRecord>.of(
|
|
(initialSilentAddresses ?? []).toSet(),
|
|
),
|
|
receivedSPAddresses = ObservableList<BitcoinReceivedSPAddressRecord>.of(
|
|
(initialReceivedSPAddresses ?? []).toSet(),
|
|
),
|
|
super(walletInfo) {
|
|
silentPaymentWallet = SilentPaymentOwner.fromBip32(hdWallet);
|
|
silentPaymentWallets = [silentPaymentWallet!];
|
|
}
|
|
|
|
@observable
|
|
SilentPaymentOwner? silentPaymentWallet;
|
|
final ObservableList<BitcoinSilentPaymentAddressRecord> silentPaymentAddresses;
|
|
final ObservableList<BitcoinReceivedSPAddressRecord> receivedSPAddresses;
|
|
|
|
@observable
|
|
List<SilentPaymentOwner> silentPaymentWallets = [];
|
|
|
|
@observable
|
|
String? activeSilentAddress;
|
|
|
|
@override
|
|
Future<void> init() async {
|
|
await generateInitialAddresses(addressType: SegwitAddresType.p2wpkh);
|
|
|
|
if (!isHardwareWallet) {
|
|
await generateInitialAddresses(addressType: P2pkhAddressType.p2pkh);
|
|
await generateInitialAddresses(addressType: P2shAddressType.p2wpkhInP2sh);
|
|
await generateInitialAddresses(addressType: SegwitAddresType.p2tr);
|
|
await generateInitialAddresses(addressType: SegwitAddresType.p2wsh);
|
|
}
|
|
|
|
if (silentPaymentAddresses.length == 0) {
|
|
Bip32Path? oldSpendPath;
|
|
Bip32Path? oldScanPath;
|
|
|
|
for (final derivationInfo in walletInfo.derivations ?? <DerivationInfo>[]) {
|
|
if (derivationInfo.description?.contains("SP") ?? false) {
|
|
if (derivationInfo.description?.toLowerCase().contains("spend") ?? false) {
|
|
oldSpendPath = Bip32PathParser.parse(derivationInfo.derivationPath ?? "");
|
|
} else if (derivationInfo.description?.toLowerCase().contains("scan") ?? false) {
|
|
oldScanPath = Bip32PathParser.parse(derivationInfo.derivationPath ?? "");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (oldSpendPath != null && oldScanPath != null) {
|
|
final oldSpendPriv = hdWallet.derive(oldSpendPath).privateKey;
|
|
final oldScanPriv = hdWallet.derive(oldScanPath).privateKey;
|
|
|
|
final oldSilentPaymentWallet = SilentPaymentOwner(
|
|
b_scan: ECPrivate(oldScanPriv),
|
|
b_spend: ECPrivate(oldSpendPriv),
|
|
B_scan: ECPublic.fromBip32(oldScanPriv.publicKey),
|
|
B_spend: ECPublic.fromBip32(oldSpendPriv.publicKey),
|
|
version: 0,
|
|
);
|
|
silentPaymentWallets.add(oldSilentPaymentWallet);
|
|
|
|
silentPaymentAddresses.addAll(
|
|
[
|
|
BitcoinSilentPaymentAddressRecord(
|
|
oldSilentPaymentWallet.toString(),
|
|
labelIndex: 1,
|
|
name: "",
|
|
addressType: SilentPaymentsAddresType.p2sp,
|
|
derivationPath: oldSpendPath.toString(),
|
|
isHidden: true,
|
|
),
|
|
BitcoinSilentPaymentAddressRecord(
|
|
oldSilentPaymentWallet.toLabeledSilentPaymentAddress(0).toString(),
|
|
name: "",
|
|
labelIndex: 0,
|
|
labelHex: BytesUtils.toHexString(oldSilentPaymentWallet.generateLabel(0)),
|
|
addressType: SilentPaymentsAddresType.p2sp,
|
|
derivationPath: oldSpendPath.toString(),
|
|
isHidden: true,
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
silentPaymentAddresses.addAll([
|
|
BitcoinSilentPaymentAddressRecord(
|
|
silentPaymentWallet.toString(),
|
|
labelIndex: 1,
|
|
name: "",
|
|
addressType: SilentPaymentsAddresType.p2sp,
|
|
),
|
|
BitcoinSilentPaymentAddressRecord(
|
|
silentPaymentWallet!.toLabeledSilentPaymentAddress(0).toString(),
|
|
name: "",
|
|
labelIndex: 0,
|
|
labelHex: BytesUtils.toHexString(silentPaymentWallet!.generateLabel(0)),
|
|
addressType: SilentPaymentsAddresType.p2sp,
|
|
),
|
|
]);
|
|
}
|
|
|
|
await updateAddressesInBox();
|
|
}
|
|
|
|
@override
|
|
@computed
|
|
String get address {
|
|
if (addressPageType == SilentPaymentsAddresType.p2sp) {
|
|
if (activeSilentAddress != null) {
|
|
return activeSilentAddress!;
|
|
}
|
|
|
|
return silentPaymentWallet.toString();
|
|
}
|
|
|
|
return super.address;
|
|
}
|
|
|
|
@override
|
|
set address(String addr) {
|
|
if (addr == "Silent Payments" && SilentPaymentsAddresType.p2sp != addressPageType) {
|
|
return;
|
|
}
|
|
|
|
if (addressPageType == SilentPaymentsAddresType.p2sp) {
|
|
late BitcoinSilentPaymentAddressRecord selected;
|
|
try {
|
|
selected =
|
|
silentPaymentAddresses.firstWhere((addressRecord) => addressRecord.address == addr);
|
|
} catch (_) {
|
|
selected = silentPaymentAddresses[0];
|
|
}
|
|
|
|
if (selected.labelHex != null) {
|
|
activeSilentAddress =
|
|
silentPaymentWallet!.toLabeledSilentPaymentAddress(selected.labelIndex).toString();
|
|
} else {
|
|
activeSilentAddress = silentPaymentWallet.toString();
|
|
}
|
|
return;
|
|
}
|
|
|
|
super.address = addr;
|
|
}
|
|
|
|
@override
|
|
BitcoinBaseAddress generateAddress({
|
|
required CWBitcoinDerivationType derivationType,
|
|
required bool isChange,
|
|
required int index,
|
|
required BitcoinAddressType addressType,
|
|
required BitcoinDerivationInfo derivationInfo,
|
|
}) {
|
|
final hdWallet = hdWallets[derivationType]!;
|
|
|
|
// if (OLD_DERIVATION_TYPES.contains(derivationType)) {
|
|
// 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: hdWallet,
|
|
derivationInfo: derivationInfo,
|
|
isChange: isChange,
|
|
index: index,
|
|
);
|
|
case SegwitAddresType.p2tr:
|
|
return P2trAddress.fromDerivation(
|
|
bip32: hdWallet,
|
|
derivationInfo: derivationInfo,
|
|
isChange: isChange,
|
|
index: index,
|
|
);
|
|
case SegwitAddresType.p2wsh:
|
|
return P2wshAddress.fromDerivation(
|
|
bip32: hdWallet,
|
|
derivationInfo: derivationInfo,
|
|
isChange: isChange,
|
|
index: index,
|
|
);
|
|
case P2shAddressType.p2wpkhInP2sh:
|
|
return P2shAddress.fromDerivation(
|
|
bip32: hdWallet,
|
|
derivationInfo: derivationInfo,
|
|
isChange: isChange,
|
|
index: index,
|
|
type: P2shAddressType.p2wpkhInP2sh,
|
|
);
|
|
case SegwitAddresType.p2wpkh:
|
|
return P2wpkhAddress.fromDerivation(
|
|
bip32: hdWallet,
|
|
derivationInfo: derivationInfo,
|
|
isChange: isChange,
|
|
index: index,
|
|
);
|
|
default:
|
|
throw ArgumentError('Invalid address type');
|
|
}
|
|
}
|
|
|
|
@override
|
|
@action
|
|
BaseBitcoinAddressRecord generateNewAddress({String label = ''}) {
|
|
if (addressPageType == SilentPaymentsAddresType.p2sp) {
|
|
final currentSPLabelIndex = silentPaymentAddresses
|
|
.where((addressRecord) => addressRecord.addressType != SegwitAddresType.p2tr)
|
|
.length -
|
|
1;
|
|
|
|
final address = BitcoinSilentPaymentAddressRecord(
|
|
silentPaymentWallet!.toLabeledSilentPaymentAddress(currentSPLabelIndex).toString(),
|
|
labelIndex: currentSPLabelIndex,
|
|
name: label,
|
|
labelHex: BytesUtils.toHexString(silentPaymentWallet!.generateLabel(currentSPLabelIndex)),
|
|
addressType: SilentPaymentsAddresType.p2sp,
|
|
);
|
|
|
|
silentPaymentAddresses.add(address);
|
|
Future.delayed(Duration.zero, () => updateAddressesByMatch());
|
|
|
|
return address;
|
|
}
|
|
|
|
return super.generateNewAddress(label: label);
|
|
}
|
|
|
|
@override
|
|
@action
|
|
void addBitcoinAddressTypes() {
|
|
super.addBitcoinAddressTypes();
|
|
|
|
silentPaymentAddresses.forEach((addressRecord) {
|
|
if (addressRecord.addressType != SilentPaymentsAddresType.p2sp || addressRecord.isChange) {
|
|
return;
|
|
}
|
|
|
|
if (addressRecord.address != address) {
|
|
addressesMap[addressRecord.address] = addressRecord.name.isEmpty
|
|
? "Silent Payments" + ': ${addressRecord.address}'
|
|
: "Silent Payments - " + addressRecord.name + ': ${addressRecord.address}';
|
|
} else {
|
|
addressesMap[address] = 'Active - Silent Payments' + ': $address';
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
@action
|
|
void updateAddress(String address, String label) {
|
|
super.updateAddress(address, label);
|
|
|
|
BaseBitcoinAddressRecord? foundAddress;
|
|
silentPaymentAddresses.forEach((addressRecord) {
|
|
if (addressRecord.address == address) {
|
|
foundAddress = addressRecord;
|
|
}
|
|
});
|
|
|
|
if (foundAddress != null) {
|
|
foundAddress!.setNewName(label);
|
|
|
|
final index =
|
|
silentPaymentAddresses.indexOf(foundAddress as BitcoinSilentPaymentAddressRecord);
|
|
silentPaymentAddresses.remove(foundAddress);
|
|
silentPaymentAddresses.insert(index, foundAddress as BitcoinSilentPaymentAddressRecord);
|
|
}
|
|
}
|
|
|
|
@override
|
|
@action
|
|
void updateAddressesByMatch() {
|
|
if (addressPageType == SilentPaymentsAddresType.p2sp) {
|
|
addressesByReceiveType.clear();
|
|
addressesByReceiveType.addAll(silentPaymentAddresses);
|
|
return;
|
|
}
|
|
|
|
super.updateAddressesByMatch();
|
|
}
|
|
|
|
@action
|
|
void addSilentAddresses(Iterable<BitcoinSilentPaymentAddressRecord> addresses) {
|
|
final addressesSet = this.silentPaymentAddresses.toSet();
|
|
addressesSet.addAll(addresses);
|
|
this.silentPaymentAddresses.clear();
|
|
this.silentPaymentAddresses.addAll(addressesSet);
|
|
updateAddressesByMatch();
|
|
}
|
|
|
|
@action
|
|
void addReceivedSPAddresses(Iterable<BitcoinReceivedSPAddressRecord> addresses) {
|
|
final addressesSet = this.receivedSPAddresses.toSet();
|
|
addressesSet.addAll(addresses);
|
|
this.receivedSPAddresses.clear();
|
|
this.receivedSPAddresses.addAll(addressesSet);
|
|
updateAddressesByMatch();
|
|
}
|
|
|
|
@action
|
|
void deleteSilentPaymentAddress(String address) {
|
|
final addressRecord = silentPaymentAddresses.firstWhere((addressRecord) =>
|
|
addressRecord.addressType == SilentPaymentsAddresType.p2sp &&
|
|
addressRecord.address == address);
|
|
|
|
silentPaymentAddresses.remove(addressRecord);
|
|
updateAddressesByMatch();
|
|
}
|
|
|
|
Map<String, String> get labels {
|
|
final G = ECPublic.fromBytes(BigintUtils.toBytes(Curves.generatorSecp256k1.x, length: 32));
|
|
final labels = <String, String>{};
|
|
for (int i = 0; i < silentPaymentAddresses.length; i++) {
|
|
final silentAddressRecord = silentPaymentAddresses[i];
|
|
final silentPaymentTweak = silentAddressRecord.labelHex;
|
|
|
|
if (silentPaymentTweak != null &&
|
|
SilentPaymentAddress.regex.hasMatch(silentAddressRecord.address)) {
|
|
labels[G
|
|
.tweakMul(BigintUtils.fromBytes(BytesUtils.fromHexString(silentPaymentTweak)))
|
|
.toHex()] = silentPaymentTweak;
|
|
}
|
|
}
|
|
return labels;
|
|
}
|
|
|
|
@override
|
|
@action
|
|
void updateHiddenAddresses() {
|
|
super.updateHiddenAddresses();
|
|
this.hiddenAddresses.addAll(silentPaymentAddresses
|
|
.where((addressRecord) => addressRecord.isHidden)
|
|
.map((addressRecord) => addressRecord.address));
|
|
}
|
|
|
|
Map<String, dynamic> toJson() {
|
|
final json = super.toJson();
|
|
json['silentPaymentAddresses'] =
|
|
silentPaymentAddresses.map((address) => address.toJSON()).toList();
|
|
json['receivedSPAddresses'] = receivedSPAddresses.map((address) => address.toJSON()).toList();
|
|
return json;
|
|
}
|
|
}
|