move to cleaner tx signing and stop adding derivations on address generation as we now have the info we need in the Address class. Old derivations should still be checked as not all old addresses are guaranteed to had a derivation path property

This commit is contained in:
julian 2023-04-20 18:36:43 -06:00
parent e7eec36986
commit 0734d9ee8a

View file

@ -18,6 +18,7 @@ import 'package:stackwallet/models/balance.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/address.dart'; import 'package:stackwallet/models/isar/models/blockchain_data/address.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models; import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models;
import 'package:stackwallet/models/paymint/fee_object_model.dart'; import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/models/signing_data.dart';
import 'package:stackwallet/services/coins/coin_service.dart'; import 'package:stackwallet/services/coins/coin_service.dart';
import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart';
@ -385,8 +386,6 @@ class BitcoinCashWallet extends CoinServiceAPI
int chain, int chain,
) async { ) async {
List<isar_models.Address> addressArray = []; List<isar_models.Address> addressArray = [];
int returningIndex = -1;
Map<String, Map<String, String>> derivations = {};
int gapCounter = 0; int gapCounter = 0;
for (int index = 0; for (int index = 0;
index < maxNumberOfIndexesToCheck && gapCounter < maxUnusedAddressGap; index < maxNumberOfIndexesToCheck && gapCounter < maxUnusedAddressGap;
@ -398,7 +397,6 @@ class BitcoinCashWallet extends CoinServiceAPI
final _id = "k_$index"; final _id = "k_$index";
Map<String, String> txCountCallArgs = {}; Map<String, String> txCountCallArgs = {};
final Map<String, dynamic> receivingNodes = {};
for (int j = 0; j < txCountBatchSize; j++) { for (int j = 0; j < txCountBatchSize; j++) {
final derivePath = constructDerivePath( final derivePath = constructDerivePath(
@ -1435,15 +1433,6 @@ class BitcoinCashWallet extends CoinServiceAPI
throw Exception("DerivePathType $derivePathType not supported"); throw Exception("DerivePathType $derivePathType not supported");
} }
// add generated address & info to derivations
await addDerivation(
chain: chain,
address: address,
pubKey: Format.uint8listToString(node.publicKey),
wif: node.toWIF(),
derivePathType: derivePathType,
);
return isar_models.Address( return isar_models.Address(
walletId: walletId, walletId: walletId,
value: address, value: address,
@ -1539,74 +1528,6 @@ class BitcoinCashWallet extends CoinServiceAPI
jsonDecode(derivationsString ?? "{}") as Map); jsonDecode(derivationsString ?? "{}") as Map);
} }
/// Add a single derivation to the local secure storage for [chain] and
/// [derivePathType] where [chain] must either be 1 for change or 0 for receive.
/// This will overwrite a previous entry where the address of the new derivation
/// matches a derivation currently stored.
Future<void> addDerivation({
required int chain,
required String address,
required String pubKey,
required String wif,
required DerivePathType derivePathType,
}) async {
// build lookup key
final key = _buildDerivationStorageKey(
chain: chain, derivePathType: derivePathType);
// fetch current derivations
final derivationsString = await _secureStore.read(key: key);
final derivations =
Map<String, dynamic>.from(jsonDecode(derivationsString ?? "{}") as Map);
// add derivation
derivations[address] = {
"pubKey": pubKey,
"wif": wif,
};
// save derivations
final newReceiveDerivationsString = jsonEncode(derivations);
await _secureStore.write(key: key, value: newReceiveDerivationsString);
}
/// Add multiple derivations to the local secure storage for [chain] and
/// [derivePathType] where [chain] must either be 1 for change or 0 for receive.
/// This will overwrite any previous entries where the address of the new derivation
/// matches a derivation currently stored.
/// The [derivationsToAdd] must be in the format of:
/// {
/// addressA : {
/// "pubKey": <the pubKey string>,
/// "wif": <the wif string>,
/// },
/// addressB : {
/// "pubKey": <the pubKey string>,
/// "wif": <the wif string>,
/// },
/// }
Future<void> addDerivations({
required int chain,
required DerivePathType derivePathType,
required Map<String, dynamic> derivationsToAdd,
}) async {
// build lookup key
final key = _buildDerivationStorageKey(
chain: chain, derivePathType: derivePathType);
// fetch current derivations
final derivationsString = await _secureStore.read(key: key);
final derivations =
Map<String, dynamic>.from(jsonDecode(derivationsString ?? "{}") as Map);
// add derivation
derivations.addAll(derivationsToAdd);
// save derivations
final newReceiveDerivationsString = jsonEncode(derivations);
await _secureStore.write(key: key, value: newReceiveDerivationsString);
}
Future<void> _updateUTXOs() async { Future<void> _updateUTXOs() async {
final allAddresses = await _fetchAllOwnAddresses(); final allAddresses = await _fetchAllOwnAddresses();
@ -2661,16 +2582,11 @@ class BitcoinCashWallet extends CoinServiceAPI
} }
} }
Future<Map<String, dynamic>> fetchBuildTxData( Future<List<SigningData>> fetchBuildTxData(
List<isar_models.UTXO> utxosToUse, List<isar_models.UTXO> utxosToUse,
) async { ) async {
// return data // return data
Map<String, dynamic> results = {}; List<SigningData> signingData = [];
Map<String, List<String>> addressTxid = {};
// addresses to check
List<String> addressesP2PKH = [];
List<String> addressesP2SH = [];
try { try {
// Populating the addresses to check // Populating the addresses to check
@ -2684,7 +2600,9 @@ class BitcoinCashWallet extends CoinServiceAPI
for (final output in tx["vout"] as List) { for (final output in tx["vout"] as List) {
final n = output["n"]; final n = output["n"];
if (n != null && n == utxosToUse[i].vout) { if (n != null && n == utxosToUse[i].vout) {
String address = output["scriptPubKey"]["addresses"][0] as String; String address =
output["scriptPubKey"]?["addresses"]?[0] as String? ??
output["scriptPubKey"]["address"] as String;
if (bitbox.Address.detectFormat(address) != if (bitbox.Address.detectFormat(address) !=
bitbox.Address.formatCashAddr) { bitbox.Address.formatCashAddr) {
try { try {
@ -2693,169 +2611,114 @@ class BitcoinCashWallet extends CoinServiceAPI
rethrow; rethrow;
} }
} }
if (!addressTxid.containsKey(address)) {
addressTxid[address] = <String>[]; utxosToUse[i] = utxosToUse[i].copyWith(address: address);
}
(addressTxid[address] as List).add(txid);
final deriveType = addressType(address: address);
switch (deriveType) {
case DerivePathType.bip44:
case DerivePathType.bch44:
addressesP2PKH.add(address);
break;
case DerivePathType.bip49:
addressesP2SH.add(address);
break;
default:
throw UnsupportedError(
"${deriveType.name} not supported by ${coin.prettyName}");
}
} }
} }
final derivePathType = addressType(address: utxosToUse[i].address!);
signingData.add(
SigningData(
derivePathType: derivePathType,
utxo: utxosToUse[i],
),
);
} }
// p2pkh / bip44 Map<DerivePathType, Map<String, dynamic>> receiveDerivations = {};
final p2pkhLength = addressesP2PKH.length; Map<DerivePathType, Map<String, dynamic>> changeDerivations = {};
if (p2pkhLength > 0) {
final receiveDerivationsBip44 = await _fetchDerivations(
chain: 0,
derivePathType: DerivePathType.bip44,
);
final changeDerivationsBip44 = await _fetchDerivations(
chain: 1,
derivePathType: DerivePathType.bip44,
);
final receiveDerivationsBch44 = await _fetchDerivations(
chain: 0,
derivePathType: DerivePathType.bch44,
);
final changeDerivationsBch44 = await _fetchDerivations(
chain: 1,
derivePathType: DerivePathType.bch44,
);
for (int i = 0; i < p2pkhLength; i++) {
String address = addressesP2PKH[i];
// receives for (final sd in signingData) {
final receiveDerivation = receiveDerivationsBip44[address] ?? String? pubKey;
receiveDerivationsBch44[address]; String? wif;
// if a match exists it will not be null
if (receiveDerivation != null) {
final data = P2PKH(
data: PaymentData(
pubkey: Format.stringToUint8List(
receiveDerivation["pubKey"] as String)),
network: _network,
).data;
for (String tx in addressTxid[address]!) { // fetch receiving derivations if null
results[tx] = { receiveDerivations[sd.derivePathType] ??= await _fetchDerivations(
"output": data.output, chain: 0,
"keyPair": ECPair.fromWIF( derivePathType: sd.derivePathType,
receiveDerivation["wif"] as String, );
network: _network, final receiveDerivation =
), receiveDerivations[sd.derivePathType]![sd.utxo.address!];
};
} if (receiveDerivation != null) {
} else { pubKey = receiveDerivation["pubKey"] as String;
// if its not a receive, check change wif = receiveDerivation["wif"] as String;
final changeDerivation = changeDerivationsBip44[address] ?? } else {
changeDerivationsBch44[address]; // fetch change derivations if null
// if a match exists it will not be null changeDerivations[sd.derivePathType] ??= await _fetchDerivations(
if (changeDerivation != null) { chain: 1,
final data = P2PKH( derivePathType: sd.derivePathType,
);
final changeDerivation =
changeDerivations[sd.derivePathType]![sd.utxo.address!];
if (changeDerivation != null) {
pubKey = changeDerivation["pubKey"] as String;
wif = changeDerivation["wif"] as String;
}
}
if (wif == null || pubKey == null) {
final address = await db.getAddress(walletId, sd.utxo.address!);
if (address?.derivationPath != null) {
final node = await Bip32Utils.getBip32Node(
(await mnemonicString)!,
(await mnemonicPassphrase)!,
_network,
address!.derivationPath!.value,
);
wif = node.toWIF();
pubKey = Format.uint8listToString(node.publicKey);
}
}
if (wif != null && pubKey != null) {
final PaymentData data;
final Uint8List? redeemScript;
switch (sd.derivePathType) {
case DerivePathType.bip44:
case DerivePathType.bch44:
data = P2PKH(
data: PaymentData( data: PaymentData(
pubkey: Format.stringToUint8List( pubkey: Format.stringToUint8List(pubKey),
changeDerivation["pubKey"] as String)), ),
network: _network, network: _network,
).data; ).data;
redeemScript = null;
break;
for (String tx in addressTxid[address]!) { case DerivePathType.bip49:
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
changeDerivation["wif"] as String,
network: _network,
),
};
}
}
}
}
}
// p2sh / bip49
final p2shLength = addressesP2SH.length;
if (p2shLength > 0) {
final receiveDerivations = await _fetchDerivations(
chain: 0,
derivePathType: DerivePathType.bip49,
);
final changeDerivations = await _fetchDerivations(
chain: 1,
derivePathType: DerivePathType.bip49,
);
for (int i = 0; i < p2shLength; i++) {
// receives
final receiveDerivation = receiveDerivations[addressesP2SH[i]];
// if a match exists it will not be null
if (receiveDerivation != null) {
final p2wpkh = P2WPKH(
data: PaymentData(
pubkey: Format.stringToUint8List(
receiveDerivation["pubKey"] as String)),
network: _network)
.data;
final redeemScript = p2wpkh.output;
final data =
P2SH(data: PaymentData(redeem: p2wpkh), network: _network).data;
for (String tx in addressTxid[addressesP2SH[i]]!) {
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
receiveDerivation["wif"] as String,
network: _network,
),
"redeemScript": redeemScript,
};
}
} else {
// if its not a receive, check change
final changeDerivation = changeDerivations[addressesP2SH[i]];
// if a match exists it will not be null
if (changeDerivation != null) {
final p2wpkh = P2WPKH( final p2wpkh = P2WPKH(
data: PaymentData( data: PaymentData(
pubkey: Format.stringToUint8List( pubkey: Format.stringToUint8List(pubKey),
changeDerivation["pubKey"] as String)), ),
network: _network) network: _network,
.data; ).data;
redeemScript = p2wpkh.output;
data = P2SH(
data: PaymentData(redeem: p2wpkh),
network: _network,
).data;
break;
final redeemScript = p2wpkh.output; default:
throw Exception("DerivePathType unsupported");
final data =
P2SH(data: PaymentData(redeem: p2wpkh), network: _network)
.data;
for (String tx in addressTxid[addressesP2SH[i]]!) {
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
changeDerivation["wif"] as String,
network: _network,
),
"redeemScript": redeemScript,
};
}
}
} }
final keyPair = ECPair.fromWIF(
wif,
network: _network,
);
sd.redeemScript = redeemScript;
sd.output = data.output;
sd.keyPair = keyPair;
} }
} }
return results; return signingData;
} catch (e, s) { } catch (e, s) {
Logging.instance Logging.instance
.log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error); .log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error);
@ -2866,7 +2729,7 @@ class BitcoinCashWallet extends CoinServiceAPI
/// Builds and signs a transaction /// Builds and signs a transaction
Future<Map<String, dynamic>> buildTransaction({ Future<Map<String, dynamic>> buildTransaction({
required List<isar_models.UTXO> utxosToUse, required List<isar_models.UTXO> utxosToUse,
required Map<String, dynamic> utxoSigningData, required List<SigningData> utxoSigningData,
required List<String> recipients, required List<String> recipients,
required List<int> satoshiAmounts, required List<int> satoshiAmounts,
}) async { }) async {
@ -2897,7 +2760,8 @@ class BitcoinCashWallet extends CoinServiceAPI
for (var utxo in _utxos) { for (var utxo in _utxos) {
// add the utxo as an input for the transaction // add the utxo as an input for the transaction
builder.addInput(utxo.txid, utxo.vout); builder.addInput(utxo.txid, utxo.vout);
final ec = utxoSigningData[utxo.txid]["keyPair"] as ECPair; final ec =
utxoSigningData.firstWhere((e) => e.utxo.txid == utxo.txid).keyPair!;
final bitboxEC = bitbox.ECPair.fromWIF(ec.toWIF()); final bitboxEC = bitbox.ECPair.fromWIF(ec.toWIF());