LCOV - code coverage report
Current view: top level - crypto/utxo/entities/raw_transaction - input.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 82.3 % 181 149
Test Date: 2025-01-30 01:10:00 Functions: - 0 0

            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              : }
        

Generated by: LCOV version 2.0-1