WIP: coinlib 2 migration (taproot txns on btc testnet tested working)

This commit is contained in:
julian 2024-04-15 15:48:55 -06:00
parent 985e269993
commit 023bad0c70
2 changed files with 149 additions and 58 deletions

View file

@ -11,6 +11,7 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:bitcoindart/bitcoindart.dart'; import 'package:bitcoindart/bitcoindart.dart';
// import 'package:coinlib_flutter/coinlib_flutter.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart'; import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart'; import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
@ -40,3 +41,27 @@ class SigningData {
"}"; "}";
} }
} }
// class SigningData {
// SigningData({
// required this.derivePathType,
// required this.utxo,
// this.keyPair,
// this.redeemScript,
// });
//
// final DerivePathType derivePathType;
// final UTXO utxo;
// ({ECPrivateKey privateKey, ECPublicKey publicKey})? keyPair;
// Uint8List? redeemScript;
//
// @override
// String toString() {
// return "SigningData{\n"
// " derivePathType: $derivePathType,\n"
// " utxo: $utxo,\n"
// " keyPair: $keyPair,\n"
// " redeemScript: $redeemScript,\n"
// "}";
// }
// }

View file

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:math'; import 'dart:math';
import 'dart:typed_data';
import 'package:bip47/src/util.dart'; import 'package:bip47/src/util.dart';
import 'package:bitcoindart/bitcoindart.dart' as bitcoindart; import 'package:bitcoindart/bitcoindart.dart' as bitcoindart;
@ -599,7 +600,7 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
// final coinlib.Input input; // final coinlib.Input input;
final pubKey = keys.publicKey.data; final pubKey = keys.publicKey.data;
final bitcoindart.PaymentData data; final bitcoindart.PaymentData? data;
switch (sd.derivePathType) { switch (sd.derivePathType) {
case DerivePathType.bip44: case DerivePathType.bip44:
@ -651,12 +652,20 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
.data; .data;
break; break;
case DerivePathType.bip86:
data = null;
break;
default: default:
throw Exception("DerivePathType unsupported"); throw Exception("DerivePathType unsupported");
} }
// sd.output = input.script!.compiled; // sd.output = input.script!.compiled;
sd.output = data.output!;
if (sd.derivePathType != DerivePathType.bip86) {
sd.output = data!.output!;
}
sd.keyPair = bitcoindart.ECPair.fromPrivateKey( sd.keyPair = bitcoindart.ECPair.fromPrivateKey(
keys.privateKey.data, keys.privateKey.data,
compressed: keys.privateKey.compressed, compressed: keys.privateKey.compressed,
@ -680,57 +689,87 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
Logging.instance Logging.instance
.log("Starting buildTransaction ----------", level: LogLevel.Info); .log("Starting buildTransaction ----------", level: LogLevel.Info);
// TODO: use coinlib
// Check if any txData.recipients are taproot/P2TR outputs.
bool hasTaprootOutput = false;
for (final recipient in txData.recipients!) {
if (cryptoCurrency.addressType(address: recipient.address) ==
DerivePathType.bip86) {
hasTaprootOutput = true;
}
}
if (hasTaprootOutput) {
// Use Coinlib to construct taproot transaction.
// TODO [prio=high]: Implement taproot transaction construction.
}
final txb = bitcoindart.TransactionBuilder(
network: bitcoindart.NetworkType(
messagePrefix: cryptoCurrency.networkParams.messagePrefix,
bech32: cryptoCurrency.networkParams.bech32Hrp,
bip32: bitcoindart.Bip32Type(
public: cryptoCurrency.networkParams.pubHDPrefix,
private: cryptoCurrency.networkParams.privHDPrefix,
),
pubKeyHash: cryptoCurrency.networkParams.p2pkhPrefix,
scriptHash: cryptoCurrency.networkParams.p2shPrefix,
wif: cryptoCurrency.networkParams.wifPrefix,
),
maximumFeeRate: maximumFeerate,
);
const version = 1; // TODO possibly override this for certain coins?
txb.setVersion(version);
// temp tx data to show in gui while waiting for real data from server // temp tx data to show in gui while waiting for real data from server
final List<InputV2> tempInputs = []; final List<InputV2> tempInputs = [];
final List<OutputV2> tempOutputs = []; final List<OutputV2> tempOutputs = [];
final List<coinlib.Output> prevOuts = [];
coinlib.Transaction clTx = coinlib.Transaction(
version: 1, // TODO: check if we can use 3 (as is default in coinlib)
inputs: [],
outputs: [],
);
// Add transaction inputs // Add transaction inputs
for (var i = 0; i < utxoSigningData.length; i++) { for (var i = 0; i < utxoSigningData.length; i++) {
final txid = utxoSigningData[i].utxo.txid; final txid = utxoSigningData[i].utxo.txid;
txb.addInput(
txid, final hash = Uint8List.fromList(txid.fromHex.reversed.toList());
final prevOutpoint = coinlib.OutPoint(
hash,
utxoSigningData[i].utxo.vout, utxoSigningData[i].utxo.vout,
null,
utxoSigningData[i].output!,
cryptoCurrency.networkParams.bech32Hrp,
); );
final prevOutput = coinlib.Output.fromAddress(
BigInt.from(utxoSigningData[i].utxo.value),
coinlib.Address.fromString(
utxoSigningData[i].utxo.address!,
cryptoCurrency.networkParams,
),
);
prevOuts.add(prevOutput);
final coinlib.Input input;
switch (utxoSigningData[i].derivePathType) {
case DerivePathType.bip44:
case DerivePathType.bch44:
input = coinlib.P2PKHInput(
prevOut: prevOutpoint,
// publicKey: utxoSigningData[i].keyPair!.publicKey,
publicKey: coinlib.ECPublicKey(
utxoSigningData[i].keyPair!.publicKey,
),
sequence: 0xffffffff - 1,
);
// TODO: fix this as it is (probably) wrong!
case DerivePathType.bip49:
input = coinlib.P2SHMultisigInput(
prevOut: prevOutpoint,
program: coinlib.MultisigProgram.decompile(
utxoSigningData[i].redeemScript!,
),
sequence: 0xffffffff - 1,
);
case DerivePathType.bip84:
input = coinlib.P2WPKHInput(
prevOut: prevOutpoint,
// publicKey: utxoSigningData[i].keyPair!.publicKey,
publicKey: coinlib.ECPublicKey(
utxoSigningData[i].keyPair!.publicKey,
),
sequence: 0xffffffff - 1,
);
case DerivePathType.bip86:
input = coinlib.TaprootKeyInput(prevOut: prevOutpoint);
default:
throw UnsupportedError(
"Unknown derivation path type found: ${utxoSigningData[i].derivePathType}",
);
}
clTx = clTx.addInput(input);
tempInputs.add( tempInputs.add(
InputV2.isarCantDoRequiredInDefaultConstructor( InputV2.isarCantDoRequiredInDefaultConstructor(
scriptSigHex: txb.inputs.first.script?.toHex, scriptSigHex: input.scriptSig.toHex,
scriptSigAsm: null, scriptSigAsm: null,
sequence: 0xffffffff - 1, sequence: 0xffffffff - 1,
outpoint: OutpointV2.isarCantDoRequiredInDefaultConstructor( outpoint: OutpointV2.isarCantDoRequiredInDefaultConstructor(
@ -751,12 +790,18 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
// Add transaction output // Add transaction output
for (var i = 0; i < txData.recipients!.length; i++) { for (var i = 0; i < txData.recipients!.length; i++) {
txb.addOutput( final address = coinlib.Address.fromString(
normalizeAddress(txData.recipients![i].address), normalizeAddress(txData.recipients![i].address),
txData.recipients![i].amount.raw.toInt(), cryptoCurrency.networkParams,
cryptoCurrency.networkParams.bech32Hrp,
); );
final output = coinlib.Output.fromAddress(
txData.recipients![i].amount.raw,
address,
);
clTx = clTx.addOutput(output);
tempOutputs.add( tempOutputs.add(
OutputV2.isarCantDoRequiredInDefaultConstructor( OutputV2.isarCantDoRequiredInDefaultConstructor(
scriptPubKeyHex: "000000", scriptPubKeyHex: "000000",
@ -779,13 +824,37 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
try { try {
// Sign the transaction accordingly // Sign the transaction accordingly
for (var i = 0; i < utxoSigningData.length; i++) { for (var i = 0; i < utxoSigningData.length; i++) {
txb.sign( final value = BigInt.from(utxoSigningData[i].utxo.value);
vin: i, coinlib.ECPrivateKey key = coinlib.ECPrivateKey(
keyPair: utxoSigningData[i].keyPair!, utxoSigningData[i].keyPair!.privateKey!,
witnessValue: utxoSigningData[i].utxo.value, compressed: utxoSigningData[i].keyPair!.compressed,
redeemScript: utxoSigningData[i].redeemScript,
overridePrefix: cryptoCurrency.networkParams.bech32Hrp,
); );
if (clTx.inputs[i] is coinlib.TaprootKeyInput) {
final taproot = coinlib.Taproot(
internalKey: coinlib.ECPublicKey(
utxoSigningData[i].keyPair!.publicKey,
),
);
key = taproot.tweakPrivateKey(key);
}
clTx = clTx.sign(
inputN: i,
value: value,
// key: utxoSigningData[i].keyPair!.privateKey,
key: key,
prevOuts: prevOuts,
);
// txb.sign(
// vin: i,
// keyPair: utxoSigningData[i].keyPair!,
// witnessValue: utxoSigningData[i].utxo.value,
// redeemScript: utxoSigningData[i].redeemScript,
// overridePrefix: cryptoCurrency.networkParams.bech32Hrp,
// );
} }
} catch (e, s) { } catch (e, s) {
Logging.instance.log("Caught exception while signing transaction: $e\n$s", Logging.instance.log("Caught exception while signing transaction: $e\n$s",
@ -793,22 +862,19 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
rethrow; rethrow;
} }
final builtTx = txb.build(cryptoCurrency.networkParams.bech32Hrp);
final vSize = builtTx.virtualSize();
return txData.copyWith( return txData.copyWith(
raw: builtTx.toHex(), raw: clTx.toHex(),
vSize: vSize, vSize: clTx.size,
tempTx: TransactionV2( tempTx: TransactionV2(
walletId: walletId, walletId: walletId,
blockHash: null, blockHash: null,
hash: builtTx.getId(), hash: clTx.hashHex,
txid: builtTx.getId(), txid: clTx.txid,
height: null, height: null,
timestamp: DateTime.timestamp().millisecondsSinceEpoch ~/ 1000, timestamp: DateTime.timestamp().millisecondsSinceEpoch ~/ 1000,
inputs: List.unmodifiable(tempInputs), inputs: List.unmodifiable(tempInputs),
outputs: List.unmodifiable(tempOutputs), outputs: List.unmodifiable(tempOutputs),
version: version, version: clTx.version,
type: type:
tempOutputs.map((e) => e.walletOwns).fold(true, (p, e) => p &= e) && tempOutputs.map((e) => e.walletOwns).fold(true, (p, e) => p &= e) &&
txData.paynymAccountLite == null txData.paynymAccountLite == null