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.0 % 172 141
Test Date: 2025-06-07 01:20:49 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 => 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              : }
        

Generated by: LCOV version 2.0-1