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