dev
dart 140 lines 5.29 KB
Raw
1 import "dart:typed_data";
2
3 import "package:ledger_bitcoin/src/psbt/constants.dart";
4 import "package:ledger_bitcoin/src/psbt/psbtv2.dart";
5 import "package:ledger_bitcoin/src/utils/buffer_writer.dart";
6
7 /// This roughly implements the "input finalizer" role of BIP370 (PSBTv2
8 /// https://github.com/bitcoin/bips/blob/master/bip-0370.mediawiki). However
9 /// the role is documented in BIP174 (PSBTv0
10 /// https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki).
11 ///
12 /// Verify that all inputs have a signature, and set inputFinalScriptwitness
13 /// and/or inputFinalScriptSig depending on the type of the spent outputs. Clean
14 /// fields that aren't useful anymore, partial signatures, redeem script and
15 /// derivation paths.
16 ///
17 /// @param psbt The psbt with all signatures added as partial sigs, either
18 /// through PSBT_IN_PARTIAL_SIG or PSBT_IN_TAP_KEY_SIG
19 extension InputFinalizer on PsbtV2 {
20 void finalizeV0() {
21 // First check that each input has a signature
22 for (var i = 0; i < getGlobalInputCount(); i++) {
23 if (_isFinalized(i)) continue;
24
25 final legacyPubkeys = getInputKeyDatas(i, PSBTIn.partialSig);
26 final taprootSig = getInputTapKeySig(i);
27 if (legacyPubkeys.isEmpty && taprootSig == null) {
28 continue;
29 // throw Exception('No signature for input $i present');
30 }
31 if (legacyPubkeys.isNotEmpty) {
32 if (legacyPubkeys.length > 1) {
33 throw Exception('Expected exactly one signature, got ${legacyPubkeys.length}');
34 }
35 if (taprootSig != null) {
36 throw Exception('Both taproot and non-taproot signatures present.');
37 }
38
39 final isSegwitV0 = getInputWitnessUtxo(i) != null;
40 final redeemScript = getInputRedeemScript(i);
41 final isWrappedSegwit = redeemScript != null;
42 final signature = getInputPartialSig(i, legacyPubkeys[0]);
43 if (signature == null) {
44 throw Exception('Expected partial signature for input $i');
45 }
46 if (isSegwitV0) {
47 final witnessBuf = BufferWriter()
48 ..writeVarInt(2)
49 ..writeVarInt(signature.length)
50 ..writeSlice(signature)
51 ..writeVarInt(legacyPubkeys[0].length)
52 ..writeSlice(legacyPubkeys[0]);
53 setInputFinalScriptwitness(i, witnessBuf.buffer());
54 if (isWrappedSegwit) {
55 if (redeemScript.isEmpty) {
56 throw Exception("Expected non-empty redeemscript. Can't finalize input $i");
57 }
58 final scriptSigBuf = BufferWriter()
59 ..writeUInt8(redeemScript.length) // Push redeemScript length
60 ..writeSlice(redeemScript);
61 setInputFinalScriptsig(i, scriptSigBuf.buffer());
62 }
63 } else {
64 // Legacy input
65 final scriptSig = BufferWriter();
66 _writePush(scriptSig, signature);
67 _writePush(scriptSig, legacyPubkeys[0]);
68 setInputFinalScriptsig(i, scriptSig.buffer());
69 }
70 } else {
71 // Taproot input
72 final signature = getInputTapKeySig(i);
73 if (signature == null) {
74 throw Exception("No taproot signature found");
75 }
76 if (signature.length != 64 && signature.length != 65) {
77 throw Exception("Unexpected length of schnorr signature.");
78 }
79 final witnessBuf = BufferWriter()
80 ..writeVarInt(1)
81 ..writeVarSlice(signature);
82 setInputFinalScriptwitness(i, witnessBuf.buffer());
83 }
84 clearFinalizedInput(i);
85 }
86 }
87
88 /// Deletes fields that are no longer necessary from the psbt.
89 ///
90 /// Note, the spec doesn't say anything about removing output fields
91 /// like PSBT_OUT_BIP32_DERIVATION_PATH and others, so we keep them
92 /// without actually knowing why. I think we should remove them too.
93 void clearFinalizedInput(int inputIndex) {
94 final keyTypes = [
95 PSBTIn.bip32Derivation,
96 PSBTIn.partialSig,
97 PSBTIn.tapBip32Derivation,
98 PSBTIn.tapKeySig,
99 ];
100 final witnessUtxoAvailable = getInputWitnessUtxo(inputIndex) != null;
101 final nonWitnessUtxoAvailable = getInputNonWitnessUtxo(inputIndex) != null;
102 if (witnessUtxoAvailable && nonWitnessUtxoAvailable) {
103 // Remove NON_WITNESS_UTXO for segwit v0 as it's only needed while signing.
104 // Segwit v1 doesn't have NON_WITNESS_UTXO set.
105 // See https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#cite_note-7
106 keyTypes.add(PSBTIn.nonWitnessUTXO);
107 }
108 deleteInputEntries(inputIndex, keyTypes);
109 }
110
111 /// Writes a script push operation to buf, which looks different
112 /// depending on the size of the data. See
113 /// https://en.bitcoin.it/wiki/Script#finalants
114 ///
115 /// [buf] the BufferWriter to write to
116 /// [data] the Buffer to be pushed.
117 void _writePush(BufferWriter buf, Uint8List data) {
118 if (data.length <= 75) {
119 buf.writeUInt8(data.length);
120 } else if (data.length <= 256) {
121 buf.writeUInt8(76);
122 buf.writeUInt8(data.length);
123 } else if (data.length <= 256 * 256) {
124 buf.writeUInt8(77);
125 final b = ByteData(2)..setUint16(0, data.length, Endian.little);
126 buf.writeSlice(b.buffer.asUint8List());
127 }
128 buf.writeSlice(data);
129 }
130
131 bool _isFinalized(int i) {
132 if (getInputFinalScriptsig(i) != null) return true;
133 try {
134 getInputFinalScriptwitness(i);
135 return true;
136 } catch (_) {
137 return false;
138 }
139 }
140 }