mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-06-28 20:39:51 +00:00
144 lines
5.3 KiB
Dart
144 lines
5.3 KiB
Dart
|
import "dart:typed_data";
|
||
|
|
||
|
import "package:ledger_bitcoin/src/psbt/constants.dart";
|
||
|
import "package:ledger_bitcoin/src/psbt/psbtv2.dart";
|
||
|
import "package:ledger_bitcoin/src/utils/buffer_writer.dart";
|
||
|
|
||
|
/// This roughly implements the "input finalizer" role of BIP370 (PSBTv2
|
||
|
/// https://github.com/bitcoin/bips/blob/master/bip-0370.mediawiki). However
|
||
|
/// the role is documented in BIP174 (PSBTv0
|
||
|
/// https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki).
|
||
|
///
|
||
|
/// Verify that all inputs have a signature, and set inputFinalScriptwitness
|
||
|
/// and/or inputFinalScriptSig depending on the type of the spent outputs. Clean
|
||
|
/// fields that aren't useful anymore, partial signatures, redeem script and
|
||
|
/// derivation paths.
|
||
|
///
|
||
|
/// @param psbt The psbt with all signatures added as partial sigs, either
|
||
|
/// through PSBT_IN_PARTIAL_SIG or PSBT_IN_TAP_KEY_SIG
|
||
|
extension InputFinalizer on PsbtV2 {
|
||
|
void finalizeV0() {
|
||
|
|
||
|
// First check that each input has a signature
|
||
|
for (var i = 0; i < getGlobalInputCount(); i++) {
|
||
|
if (_isFinalized(i)) continue;
|
||
|
|
||
|
final legacyPubkeys = getInputKeyDatas(i, PSBTIn.partialSig);
|
||
|
final taprootSig = getInputTapKeySig(i);
|
||
|
if (legacyPubkeys.isEmpty && taprootSig == null) {
|
||
|
continue;
|
||
|
// throw Exception('No signature for input $i present');
|
||
|
}
|
||
|
if (legacyPubkeys.isNotEmpty) {
|
||
|
if (legacyPubkeys.length > 1) {
|
||
|
throw Exception(
|
||
|
'Expected exactly one signature, got ${legacyPubkeys.length}');
|
||
|
}
|
||
|
if (taprootSig != null) {
|
||
|
throw Exception('Both taproot and non-taproot signatures present.');
|
||
|
}
|
||
|
|
||
|
final isSegwitV0 = getInputWitnessUtxo(i) != null;
|
||
|
final redeemScript = getInputRedeemScript(i);
|
||
|
final isWrappedSegwit = redeemScript != null;
|
||
|
final signature = getInputPartialSig(i, legacyPubkeys[0]);
|
||
|
if (signature == null) {
|
||
|
throw Exception('Expected partial signature for input $i');
|
||
|
}
|
||
|
if (isSegwitV0) {
|
||
|
final witnessBuf = BufferWriter()
|
||
|
..writeVarInt(2)
|
||
|
..writeVarInt(signature.length)
|
||
|
..writeSlice(signature)
|
||
|
..writeVarInt(legacyPubkeys[0].length)
|
||
|
..writeSlice(legacyPubkeys[0]);
|
||
|
setInputFinalScriptwitness(i, witnessBuf.buffer());
|
||
|
if (isWrappedSegwit) {
|
||
|
if (redeemScript.isEmpty) {
|
||
|
throw Exception(
|
||
|
"Expected non-empty redeemscript. Can't finalize intput $i");
|
||
|
}
|
||
|
final scriptSigBuf = BufferWriter()
|
||
|
..writeUInt8(redeemScript.length) // Push redeemScript length
|
||
|
..writeSlice(redeemScript);
|
||
|
setInputFinalScriptsig(i, scriptSigBuf.buffer());
|
||
|
}
|
||
|
} else {
|
||
|
// Legacy input
|
||
|
final scriptSig = BufferWriter();
|
||
|
_writePush(scriptSig, signature);
|
||
|
_writePush(scriptSig, legacyPubkeys[0]);
|
||
|
setInputFinalScriptsig(i, scriptSig.buffer());
|
||
|
}
|
||
|
} else {
|
||
|
// Taproot input
|
||
|
final signature = getInputTapKeySig(i);
|
||
|
if (signature == null) {
|
||
|
throw Exception("No taproot signature found");
|
||
|
}
|
||
|
if (signature.length != 64 && signature.length != 65) {
|
||
|
throw Exception("Unexpected length of schnorr signature.");
|
||
|
}
|
||
|
final witnessBuf = BufferWriter()
|
||
|
..writeVarInt(1)
|
||
|
..writeVarSlice(signature);
|
||
|
setInputFinalScriptwitness(i, witnessBuf.buffer());
|
||
|
}
|
||
|
clearFinalizedInput(i);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Deletes fields that are no longer neccesary from the psbt.
|
||
|
///
|
||
|
/// Note, the spec doesn't say anything about removing ouput fields
|
||
|
/// like PSBT_OUT_BIP32_DERIVATION_PATH and others, so we keep them
|
||
|
/// without actually knowing why. I think we should remove them too.
|
||
|
void clearFinalizedInput(int inputIndex) {
|
||
|
final keyTypes = [
|
||
|
PSBTIn.bip32Derivation,
|
||
|
PSBTIn.partialSig,
|
||
|
PSBTIn.tapBip32Derivation,
|
||
|
PSBTIn.tapKeySig,
|
||
|
];
|
||
|
final witnessUtxoAvailable = getInputWitnessUtxo(inputIndex) != null;
|
||
|
final nonWitnessUtxoAvailable = getInputNonWitnessUtxo(inputIndex) != null;
|
||
|
if (witnessUtxoAvailable && nonWitnessUtxoAvailable) {
|
||
|
// Remove NON_WITNESS_UTXO for segwit v0 as it's only needed while signing.
|
||
|
// Segwit v1 doesn't have NON_WITNESS_UTXO set.
|
||
|
// See https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#cite_note-7
|
||
|
keyTypes.add(PSBTIn.nonWitnessUTXO);
|
||
|
}
|
||
|
deleteInputEntries(inputIndex, keyTypes);
|
||
|
}
|
||
|
|
||
|
/// Writes a script push operation to buf, which looks different
|
||
|
/// depending on the size of the data. See
|
||
|
/// https://en.bitcoin.it/wiki/Script#finalants
|
||
|
///
|
||
|
/// [buf] the BufferWriter to write to
|
||
|
/// [data] the Buffer to be pushed.
|
||
|
void _writePush(BufferWriter buf, Uint8List data) {
|
||
|
if (data.length <= 75) {
|
||
|
buf.writeUInt8(data.length);
|
||
|
} else if (data.length <= 256) {
|
||
|
buf.writeUInt8(76);
|
||
|
buf.writeUInt8(data.length);
|
||
|
} else if (data.length <= 256 * 256) {
|
||
|
buf.writeUInt8(77);
|
||
|
final b = ByteData(2)..setUint16(0, data.length, Endian.little);
|
||
|
buf.writeSlice(b.buffer.asUint8List());
|
||
|
}
|
||
|
buf.writeSlice(data);
|
||
|
}
|
||
|
|
||
|
bool _isFinalized(int i) {
|
||
|
if (getInputFinalScriptsig(i) != null) return true;
|
||
|
try {
|
||
|
getInputFinalScriptwitness(i);
|
||
|
return true;
|
||
|
} catch (_) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|