mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-03-25 08:38:45 +00:00
WIP: coinlib 2 migration (taproot txns on btc testnet tested working)
This commit is contained in:
parent
985e269993
commit
023bad0c70
2 changed files with 149 additions and 58 deletions
|
@ -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"
|
||||||
|
// "}";
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue