Line data Source code
1 : import 'dart:typed_data';
2 :
3 : import 'package:convert/convert.dart';
4 : import 'package:walletkit_dart/src/crypto/utxo/entities/script.dart';
5 : import 'package:walletkit_dart/src/crypto/utxo/entities/op_codes.dart';
6 : import 'package:walletkit_dart/src/utils/int.dart';
7 : import 'package:walletkit_dart/src/utils/var_uint.dart';
8 : import 'package:walletkit_dart/walletkit_dart.dart';
9 :
10 : const output_index_length = 4;
11 : const sequence_length = 4;
12 :
13 : sealed class Input {
14 : final Uint8List txid;
15 : final int vout;
16 : final Uint8List? _scriptSig;
17 : final Uint8List? _wittnessScript;
18 : final BigInt? value;
19 : final Uint8List? _prevScriptPubKey;
20 :
21 6 : const Input({
22 : required this.txid,
23 : required this.vout,
24 : this.value,
25 : Uint8List? prevScriptPubKey,
26 : Uint8List? scriptSig,
27 : Uint8List? wittnessScript,
28 : }) : _scriptSig = scriptSig,
29 : _prevScriptPubKey = prevScriptPubKey,
30 : _wittnessScript = wittnessScript;
31 :
32 2 : BigInt get weight {
33 8 : if (_scriptSig == null || _prevScriptPubKey == null) return -1.toBI;
34 3 : return calculateWeight(_prevScriptPubKey, _scriptSig);
35 : }
36 :
37 8 : int get intValue => value != null ? value!.toInt() : 0;
38 :
39 0 : String? get scriptSigHex => _scriptSig != null ? _scriptSig.toHex : null;
40 :
41 0 : String get txIdString => hex.encode(txid);
42 :
43 13 : Uint8List get scriptSig => _scriptSig ?? Uint8List(0);
44 :
45 6 : Uint8List get wittnessScript => _wittnessScript ?? Uint8List(0);
46 :
47 : Uint8List get bytes;
48 :
49 9 : int get size => bytes.length;
50 :
51 0 : String get toHex => hex.encode(bytes);
52 :
53 14 : Uint8List get previousScriptPubKey => _prevScriptPubKey ?? Uint8List(0);
54 :
55 1 : bool get isP2SH =>
56 3 : previousScriptPubKey.length == 23 &&
57 0 : previousScriptPubKey[0] == OP_HASH160 &&
58 0 : previousScriptPubKey[1] == 0x14 &&
59 0 : previousScriptPubKey[22] == OP_EQUAL;
60 :
61 0 : bool get isP2PKH =>
62 0 : previousScriptPubKey.length == 25 &&
63 0 : previousScriptPubKey[0] == OP_DUP &&
64 0 : previousScriptPubKey[1] == OP_HASH160 &&
65 0 : previousScriptPubKey[2] == 0x14 &&
66 0 : previousScriptPubKey[23] == OP_EQUALVERIFY &&
67 0 : previousScriptPubKey[24] == OP_CHECKSIG;
68 :
69 3 : bool get isP2WPKH =>
70 9 : previousScriptPubKey.length == 22 &&
71 3 : previousScriptPubKey[0] == 0x00 &&
72 3 : previousScriptPubKey[1] == 0x14;
73 :
74 3 : bool get isP2WSH =>
75 9 : previousScriptPubKey.length == 34 &&
76 0 : previousScriptPubKey[0] == 0x00 &&
77 0 : previousScriptPubKey[1] == 0x20;
78 :
79 0 : bool get isP2PK => previousScriptPubKey.length == 35 && previousScriptPubKey[0] == 0x21;
80 :
81 12 : bool get isSegwit => isP2WPKH || isP2WSH || hasWitness;
82 :
83 10 : bool get hasWitness => _wittnessScript != null;
84 :
85 1 : Uint8List get publicKeyFromSig {
86 : /// From ScriptSig (P2PKH, P2PK)
87 3 : if (_scriptSig != null && _scriptSig.isNotEmpty) {
88 2 : final script = Script(_scriptSig);
89 :
90 3 : final publicKey = script.chunks[1].data;
91 : if (publicKey == null) {
92 0 : throw Exception("Invalid Public Key");
93 : }
94 2 : if (publicKey.length != 33) {
95 0 : throw Exception("Invalid Public Key");
96 : }
97 : return publicKey;
98 : }
99 :
100 : /// From Witness
101 3 : if (_wittnessScript != null && _wittnessScript.isNotEmpty) {
102 2 : final chunks = decodeScriptWittness(wittnessScript: _wittnessScript);
103 2 : if (chunks.length != 2) {
104 0 : throw Exception("Invalid Witness");
105 : }
106 1 : final publicKey = chunks[1];
107 2 : if (publicKey.length != 33) {
108 0 : throw Exception("Invalid Public Key");
109 : }
110 : return publicKey;
111 : }
112 :
113 0 : throw Exception("No ScriptSig or Witness found");
114 : }
115 :
116 : Input addScript({Uint8List? scriptSig, Uint8List? wittnessScript});
117 :
118 1 : BigInt calculateWeight(
119 : Uint8List prevScriptPubKey,
120 : Uint8List? scriptSig,
121 : ) {
122 1 : if (scriptSig == null || prevScriptPubKey.isEmpty) {
123 0 : return 0.toBI;
124 : }
125 :
126 3 : BigInt w = 1.toBI + getScriptWeight(prevScriptPubKey);
127 :
128 1 : if (!isP2SH) return w;
129 :
130 0 : final script = Script(scriptSig);
131 :
132 0 : Uint8List? buffer = Uint8List(0);
133 :
134 0 : for (final chunk in script.chunks) {
135 : if (buffer != null) {
136 0 : buffer = chunk.data;
137 : }
138 0 : if (chunk.opcode > OP_16) {
139 0 : return weight;
140 : }
141 : }
142 :
143 0 : if (buffer != null && buffer.isNotEmpty) {
144 0 : w += getScriptWeight(buffer);
145 : }
146 :
147 : return w;
148 : }
149 :
150 1 : BTCInput changeSequence(int sequence) {
151 1 : return BTCInput(
152 1 : txid: txid,
153 1 : vout: vout,
154 1 : value: value,
155 1 : scriptSig: _scriptSig,
156 1 : prevScriptPubKey: _prevScriptPubKey,
157 1 : wittnessScript: _wittnessScript,
158 : sequence: sequence,
159 : );
160 : }
161 : }
162 :
163 : class BTCInput extends Input {
164 : final int sequence;
165 :
166 5 : const BTCInput({
167 : required super.txid,
168 : required super.vout,
169 : required super.value,
170 : super.scriptSig,
171 : super.prevScriptPubKey,
172 : super.wittnessScript,
173 : this.sequence = 0xffffffff,
174 : });
175 :
176 2 : factory BTCInput.fromBuffer(Uint8List buffer) {
177 : var offset = 0;
178 :
179 : /// Previous Transaction Hash
180 2 : final (txid, off1) = buffer.readSlice(offset, 32);
181 2 : offset += off1;
182 :
183 : /// Previous Transaction Index
184 4 : final (vout, off2) = buffer.bytes.readUint32(offset);
185 2 : offset += off2;
186 :
187 : /// ScriptSig
188 2 : final (script, off3) = buffer.readVarSlice(offset);
189 2 : offset += off3;
190 :
191 : /// Sequence
192 4 : final (sequence, off4) = buffer.bytes.readUint32(offset);
193 2 : offset += off4;
194 :
195 2 : return BTCInput(
196 : txid: txid,
197 : vout: vout,
198 : sequence: sequence,
199 : scriptSig: script,
200 : value: null,
201 : );
202 : }
203 :
204 5 : BTCInput addScript({
205 : Uint8List? scriptSig,
206 : Uint8List? wittnessScript,
207 : }) {
208 3 : final _scriptSig = scriptSig ?? this._scriptSig;
209 3 : final _witnessScript = wittnessScript ?? _wittnessScript;
210 :
211 5 : return BTCInput(
212 5 : txid: txid,
213 5 : vout: vout,
214 : scriptSig: _scriptSig,
215 5 : prevScriptPubKey: previousScriptPubKey,
216 : wittnessScript: _witnessScript,
217 5 : value: value,
218 5 : sequence: sequence,
219 : );
220 : }
221 :
222 5 : Uint8List get bytes {
223 5 : final buffer = Uint8List(
224 40 : txid.length + output_index_length + scriptSig.length + 1 + sequence_length,
225 : );
226 :
227 : var offset = 0;
228 : // Write TXID
229 15 : offset += buffer.writeSlice(offset, txid); // Or TXID ?
230 :
231 : // Write Vout
232 20 : offset += buffer.bytes.writeUint32(offset, vout);
233 :
234 : // Write ScriptSig
235 15 : offset += buffer.writeVarSlice(offset, scriptSig);
236 :
237 : // Write Sequence
238 20 : offset += buffer.bytes.writeUint32(offset, sequence);
239 :
240 : return buffer;
241 : }
242 : }
243 :
244 : const value_length = 8;
245 : const weight_length = 4;
246 :
247 : class EC8Input extends Input {
248 2 : const EC8Input({
249 : required super.txid,
250 : required super.vout,
251 : required super.value,
252 : super.prevScriptPubKey,
253 : super.scriptSig,
254 : super.wittnessScript,
255 : });
256 :
257 1 : EC8Input addScript({
258 : Uint8List? scriptSig,
259 : Uint8List? wittnessScript,
260 : }) {
261 0 : final _scriptSig = scriptSig ?? this._scriptSig;
262 1 : final _witnessScript = wittnessScript ?? _wittnessScript;
263 :
264 1 : return EC8Input(
265 1 : txid: txid,
266 1 : vout: vout,
267 : scriptSig: _scriptSig,
268 1 : value: value,
269 1 : prevScriptPubKey: previousScriptPubKey,
270 : wittnessScript: _witnessScript,
271 : );
272 : }
273 :
274 2 : Uint8List get bytes {
275 2 : final buffer = Uint8List(
276 18 : txid.length + output_index_length + value_length + weight_length + scriptSig.length + 1,
277 : );
278 :
279 : var offset = 0;
280 : // Write TXID
281 6 : offset += buffer.writeSlice(offset, txid); // Or TXID ?
282 :
283 : // Write Vout
284 8 : offset += buffer.bytes.writeUint32(offset, vout);
285 :
286 : // Write Value
287 8 : offset += buffer.bytes.writeUint64(offset, intValue);
288 :
289 : // Write Weight
290 10 : offset += buffer.bytes.writeUint32(offset, weight.toInt());
291 :
292 : // Write ScriptSig
293 6 : offset += buffer.writeVarSlice(offset, scriptSig);
294 :
295 : return buffer;
296 : }
297 :
298 1 : Uint8List get bytesForTxId {
299 1 : final buffer = Uint8List(
300 6 : txid.length + output_index_length + value_length + weight_length + 1,
301 : );
302 :
303 : var offset = 0;
304 : // Write TXID
305 3 : offset += buffer.writeSlice(offset, txid); // Or TXID ?
306 :
307 : // Write Vout
308 4 : offset += buffer.bytes.writeUint32(offset, vout);
309 :
310 : // Write Value
311 4 : offset += buffer.bytes.writeUint64(offset, intValue);
312 :
313 : // Write Weight
314 3 : offset += buffer.bytes.writeUint32(offset, 0);
315 :
316 : // Write ScriptSig
317 3 : offset += buffer.writeVarSlice(offset, Uint8List(0));
318 :
319 : return buffer;
320 : }
321 :
322 1 : Uint8List bytesForSigning({
323 : required bool withWeight,
324 : required bool withScript,
325 : }) {
326 1 : final buffer = Uint8List(
327 3 : txid.length +
328 1 : output_index_length +
329 1 : value_length +
330 1 : (withWeight ? weight_length : 0) +
331 3 : (withScript ? scriptSig.length + 1 : 0),
332 : );
333 :
334 : var offset = 0;
335 : // Write TXID
336 3 : offset += buffer.writeSlice(offset, txid);
337 : // Write Vout
338 4 : offset += buffer.bytes.writeUint32(offset, vout);
339 : // Write Value
340 4 : offset += buffer.bytes.writeUint64(offset, intValue);
341 :
342 : // Write Weight
343 : if (withWeight) {
344 5 : offset += buffer.bytes.writeUint32(offset, weight.toInt()); // Should be 146
345 : }
346 :
347 : if (withScript) {
348 : // Write ScriptSig
349 3 : offset += buffer.writeVarSlice(offset, scriptSig);
350 : }
351 : return buffer;
352 : }
353 :
354 2 : factory EC8Input.fromBuffer(Uint8List buffer) {
355 : var offset = 0;
356 :
357 : /// Previous Transaction Hash
358 2 : final (txid, off1) = buffer.readSlice(offset, 32);
359 2 : offset += off1;
360 :
361 : /// Previous Transaction Index
362 4 : final (vout, off2) = buffer.bytes.readUint32(offset);
363 2 : offset += off2;
364 :
365 : /// Value
366 4 : final (value, off3) = buffer.bytes.readUint64(offset);
367 2 : offset += off3;
368 :
369 : /// Weight (is ignored since it is calculated)
370 4 : final (_, off4) = buffer.bytes.readUint32(offset);
371 2 : offset += off4;
372 :
373 : /// ScriptSig
374 2 : final (scriptSig, off5) = buffer.readVarSlice(offset);
375 2 : offset += off5;
376 :
377 2 : return EC8Input(
378 : txid: txid,
379 : vout: vout,
380 : scriptSig: scriptSig,
381 2 : value: BigInt.from(value),
382 : );
383 : }
384 : }
385 :
386 2 : (Uint8List, int) readScriptWittness({
387 : required Uint8List buffer,
388 : required int offset,
389 : }) {
390 4 : final (count, off1) = buffer.bytes.readVarInt(offset);
391 2 : offset += off1;
392 :
393 2 : final scripts = <Uint8List>[];
394 :
395 4 : for (var i = 0; i < count; i++) {
396 2 : final (script, off2) = buffer.readVarSlice(offset);
397 2 : offset += off2;
398 2 : scripts.add(script);
399 : }
400 :
401 2 : final wittnessScript = [
402 : count,
403 4 : for (final script in scripts) ...[
404 2 : script.length,
405 2 : ...script,
406 : ],
407 2 : ].toUint8List;
408 :
409 2 : return (wittnessScript, wittnessScript.length);
410 : }
411 :
412 1 : List<Uint8List> decodeScriptWittness({
413 : required Uint8List wittnessScript,
414 : }) {
415 1 : final scripts = <Uint8List>[];
416 :
417 : var offset = 0;
418 :
419 1 : final count = wittnessScript[offset];
420 1 : offset += 1;
421 :
422 2 : for (var i = 0; i < count; i++) {
423 1 : final length = wittnessScript[offset];
424 1 : offset += 1;
425 :
426 2 : final script = wittnessScript.sublist(offset, offset + length);
427 1 : offset += length;
428 :
429 1 : scripts.add(script);
430 : }
431 :
432 : return scripts;
433 : }
|