CakeWallet/cw_bitcoin/lib/psbt/v0_finalizer.dart
Konstantin Ullrich 82e3ebf4fa
implement-payjoin (#1949)
* Initial Payjoin

* Initial Payjoin

* More payjoin stuff

* Minor fixes

* Minor fixes

* Minor cleanup

* Minor cleanup

* Minor cleanup

* Minor cleanup

* Minor cleanup

* Minor cleanup

* Fix minor bug causes by data inconsistency in the btc utxos

* Minor cleanup

* Minor cleanup

* Minor cleanup

* Minor cleanup

* Initial Payjoin

* Initial Payjoin

* More payjoin stuff

* Minor fixes

* Minor fixes

* Minor cleanup

* Minor cleanup

* Minor cleanup

* Minor cleanup

* Minor cleanup

* Minor cleanup

* Fix minor bug causes by data inconsistency in the btc utxos

* Minor cleanup

* Minor cleanup

* Minor cleanup

* Minor cleanup

* Fix Rebase issues

* Move PJ Receiver to isolate

* Add Payjoin Setting

* Payjoin Sender are now isolated

* Added Payjoin sessions to tx overview. Fix Fee issue with payjoin

* Clean up code

* Fix taproot for payjoin

* Fix CI Errors

* Add Payjoin UI elements and details page

* Add Payjoin UI elements and details page

* Fix Translations

* feat: Detect Payjoin URIs in pasted text and show to the User sending Payjoin

* feat: rename pjUri to payjoinURI for more code clarity

* Update res/values/strings_pl.arb

Co-authored-by: cyan <cyjan@mrcyjanek.net>

* Update cw_bitcoin/lib/payjoin/manager.dart

Co-authored-by: cyan <cyjan@mrcyjanek.net>

* Update cw_bitcoin/lib/payjoin/manager.dart

Co-authored-by: cyan <cyjan@mrcyjanek.net>

* feat: Disable Payjoin per default

* feat: Disable Payjoin fully if disabled or no Inputs available

* feat: Resume Payjoin if app comes back to foreground

* chore: Revert overly aggressive code formats

* feat: show correct Payjoin amount for receivers

* feat: Improved payjoin status

* feat: Show payjoin errors on payjoin details screen

* deps: update flutter to 3.27.4

* feat: Revert localisations

* bug: Remove duplicate transaction id on payjoin details

* style: remove double await in payjoin sender

* refactor(cw_bitcoin): Refactor method signatures and convert constructor to factory

* refactor(cw_bitcoin): Refactor wallet service and PSBT signer for cleaner code

Removed unnecessary `CakeHive` dependency and refactored `BitcoinWallet` initialization to use `payjoinSessionSource`. Improved code readability in `PsbtSigner` by reformatting lines and simplifying constructor methods for `UtxoWithPrivateKey`.

* fix: Resume Payjoin Sessions and load PJUri after sleep

* feat: Add "Copy Payjoin URL button" to receive screen

* fix: Add "Payjoin enabled"-Box below QR Code on the receive screen

* fix: Set payjoin_enabled color to black independent of the theme

* refactor: Payjoin session management and cleanup unused code.

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
Co-authored-by: cyan <cyjan@mrcyjanek.net>
2025-05-12 20:33:14 +03:00

143 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;
}
}
}