LCOV - code coverage report
Current view: top level - crypto/utxo/entities/raw_transaction - raw_transaction.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 97.4 % 303 295
Test Date: 2025-06-07 01:20:49 Functions: - 0 0

            Line data    Source code
       1              : import 'dart:typed_data';
       2              : 
       3              : import 'package:collection/collection.dart';
       4              : import 'package:walletkit_dart/src/crypto/utxo/entities/payments/pk_script_converter.dart';
       5              : import 'package:walletkit_dart/src/crypto/utxo/utils/pubkey_to_address.dart';
       6              : import 'package:walletkit_dart/src/crypto/utxo/entities/raw_transaction/input.dart';
       7              : import 'package:walletkit_dart/src/crypto/utxo/entities/raw_transaction/output.dart';
       8              : import 'package:walletkit_dart/src/utils/int.dart';
       9              : import 'package:walletkit_dart/src/utils/var_uint.dart';
      10              : import 'package:walletkit_dart/walletkit_dart.dart';
      11              : 
      12              : const SEGWIT_FLAG = 0x01;
      13              : const SEGWIT_MARKER = 0x00;
      14              : 
      15              : sealed class RawTransaction {
      16              :   final int version;
      17              :   final List<Input> inputs;
      18              :   final List<Output> outputs;
      19              : 
      20              :   /// Mapping of UTXOs to generated inputs
      21              :   /// Non Null if returned from [buildUnsignedTransaction]
      22              :   final Map<ElectrumOutput, Input>? inputMap;
      23              : 
      24            1 :   BigInt get weight {
      25            2 :     return inputs.fold(
      26            1 :           0.toBI,
      27            3 :           (prev, input) => prev + input.weight,
      28            1 :         ) +
      29            2 :         outputs.fold(
      30            1 :           0.toBI,
      31            3 :           (prev, output) => prev + output.weight,
      32              :         );
      33              :   }
      34              : 
      35              :   Uint8List get bytes;
      36              : 
      37           18 :   String get asHex => bytes.toHex;
      38              : 
      39            0 :   int get size => bytes.length;
      40              : 
      41            4 :   BigInt get fee => totalInputValue - totalOutputValue;
      42              : 
      43              :   // Value of the first output
      44            0 :   BigInt get targetAmount => outputs.first.value;
      45              : 
      46            1 :   BigInt get totalInputValue {
      47            2 :     return inputs.fold(
      48            1 :       BigInt.zero,
      49            3 :       (prev, element) => prev + (element.value ?? BigInt.zero),
      50              :     );
      51              :   }
      52              : 
      53            1 :   BigInt get totalOutputValue {
      54            2 :     return outputs.fold(
      55            1 :       BigInt.zero,
      56            3 :       (prev, element) => prev + element.value,
      57              :     );
      58              :   }
      59              : 
      60            0 :   String get txid {
      61            0 :     final buffer = bytes;
      62            0 :     final hash = sha256Sha256Hash(buffer);
      63            0 :     return hash.rev.toHex;
      64              :   }
      65              : 
      66            3 :   Uint8List legacySigHash({
      67              :     required int index,
      68              :     required Uint8List prevScriptPubKey,
      69              :     required int hashType,
      70              :   }) {
      71            3 :     final copy = createCopy();
      72              : 
      73              :     // clear all scriptSigs
      74           12 :     for (int i = 0; i < copy.inputs.length; i++) {
      75           15 :       copy.inputs[i] = copy.inputs[i].addScript(
      76            6 :         scriptSig: Uint8List.fromList([]),
      77              :       );
      78              :     }
      79              : 
      80              :     // set scriptSig for inputIndex to prevScriptPubKeyHex
      81           15 :     copy.inputs[index] = copy.inputs[index].addScript(
      82              :       scriptSig: prevScriptPubKey,
      83              :     );
      84              : 
      85            6 :     final bytes = copy is EC8RawTransaction ? copy.bytesForSigning : copy.bytes;
      86              : 
      87            9 :     final buffer = Uint8List(bytes.length + 4);
      88              :     var offset = 0;
      89            6 :     offset += buffer.writeSlice(0, bytes);
      90            9 :     offset += buffer.bytes.writeUint32(offset, hashType);
      91              : 
      92            3 :     final hash = sha256Sha256Hash(buffer);
      93              : 
      94              :     return hash;
      95              :   }
      96              : 
      97            6 :   const RawTransaction({
      98              :     required this.version,
      99              :     required this.inputs,
     100              :     required this.outputs,
     101              :     this.inputMap,
     102              :   });
     103              : 
     104              :   RawTransaction createCopy();
     105              : 
     106              :   RawTransaction _addInputs(List<Input>? inputs);
     107              : 
     108            4 :   static RawTransaction build({
     109              :     required int version,
     110              :     required List<Output> outputs,
     111              :     required Map<ElectrumOutput, Input> inputMap,
     112              :     int? lockTime,
     113              :     int? validFrom,
     114              :     int? validUntil,
     115              :   }) {
     116            4 :     final inputs = inputMap.values;
     117            4 :     final btcInputs = inputs.whereType<BTCInput>();
     118            4 :     final btcOutputs = outputs.whereType<BTCOutput>();
     119              : 
     120            7 :     if (btcInputs.isNotEmpty && btcOutputs.isNotEmpty && lockTime != null) {
     121            3 :       return BTCRawTransaction(
     122              :         version: version,
     123            3 :         inputs: btcInputs.toList(),
     124            3 :         outputs: btcOutputs.toList(),
     125              :         inputMap: inputMap,
     126              :         lockTime: lockTime,
     127              :       );
     128              :     }
     129              : 
     130            1 :     final ec8Inputs = inputs.whereType<EC8Input>();
     131            1 :     final ec8Outputs = outputs.whereType<EC8Output>();
     132              : 
     133            2 :     if (ec8Inputs.isNotEmpty && ec8Outputs.isNotEmpty && validFrom != null && validUntil != null) {
     134            1 :       return EC8RawTransaction(
     135              :         version: version,
     136            1 :         inputs: ec8Inputs.toList(),
     137            1 :         outputs: ec8Outputs.toList(),
     138              :         inputMap: inputMap,
     139              :         validFrom: validFrom,
     140              :         validUntil: validUntil,
     141              :       );
     142              :     }
     143              : 
     144            0 :     throw UnimplementedError();
     145              :   }
     146              : 
     147            4 :   RawTransaction sign({
     148              :     required Uint8List seed,
     149              :     required HDWalletPath walletPath,
     150              :     required UTXONetworkType networkType,
     151              :   }) {
     152              :     assert(
     153            4 :       inputMap != null,
     154              :       'Cant sign transaction without inputs',
     155              :     );
     156              : 
     157            4 :     final signedInputs = signInputs(
     158            4 :       inputs: inputMap!,
     159              :       walletPath: walletPath,
     160              :       tx: this,
     161              :       networkType: networkType,
     162              :       seed: seed,
     163              :     );
     164              : 
     165            4 :     return _addInputs(signedInputs);
     166              :   }
     167              : }
     168              : 
     169              : ///
     170              : /// Raw Transaction Implementation for Bitcoin
     171              : ///
     172              : class BTCRawTransaction extends RawTransaction {
     173              :   final int lockTime;
     174              : 
     175              :   @override
     176              :   final List<BTCInput> inputs;
     177              : 
     178              :   @override
     179              :   final List<BTCOutput> outputs;
     180              : 
     181            5 :   const BTCRawTransaction({
     182              :     required super.version,
     183              :     required this.lockTime,
     184              :     required this.inputs,
     185              :     required this.outputs,
     186              :     super.inputMap,
     187            5 :   }) : super(
     188              :           inputs: inputs,
     189              :           outputs: outputs,
     190              :         );
     191              : 
     192            5 :   bool get hasWitness {
     193           20 :     return inputs.any((input) => input.hasWitness);
     194              :   }
     195              : 
     196            3 :   Iterable<BTCInput> get segwitInputs {
     197           12 :     return inputs.where((input) => input.hasWitness);
     198              :   }
     199              : 
     200            3 :   Iterable<BTCInput> get nonSegwitInputs {
     201           15 :     return inputs.where((input) => input.hasWitness == false);
     202              :   }
     203              : 
     204            2 :   BTCRawTransaction createCopy() {
     205            2 :     return BTCRawTransaction(
     206            2 :       version: version,
     207            2 :       lockTime: lockTime,
     208            2 :       inputs: inputs,
     209            2 :       outputs: outputs,
     210              :     );
     211              :   }
     212              : 
     213            3 :   BTCRawTransaction _addInputs(
     214              :     List<Input>? inputs,
     215              :   ) {
     216            6 :     final signedInputs = inputs?.whereType<BTCInput>().toList() ?? this.inputs;
     217              : 
     218            3 :     return BTCRawTransaction(
     219            3 :       version: version,
     220            3 :       lockTime: lockTime,
     221              :       inputs: signedInputs,
     222            3 :       outputs: outputs,
     223              :     );
     224              :   }
     225              : 
     226            2 :   factory BTCRawTransaction.fromHex(String hex) {
     227            2 :     final buffer = hex.hexToBytes;
     228              : 
     229              :     var offset = 0;
     230              : 
     231              :     /// Version
     232            4 :     final (version, length) = buffer.bytes.readUint32(offset);
     233            2 :     offset += length;
     234              : 
     235              :     /// Segwit Flag
     236           10 :     final isSegwit = buffer[offset] == SEGWIT_MARKER && buffer[offset + 1] == SEGWIT_FLAG;
     237              : 
     238              :     if (isSegwit) {
     239            2 :       offset += 2;
     240              :     }
     241              : 
     242              :     /// Inputs
     243            4 :     final (inputLength, inputLengthByteLength) = buffer.bytes.readVarInt(offset);
     244            2 :     offset += inputLengthByteLength;
     245              : 
     246            2 :     final inputs = <BTCInput>[];
     247            4 :     for (int i = 0; i < inputLength; i++) {
     248            4 :       final input = BTCInput.fromBuffer(buffer.sublist(offset));
     249            4 :       offset += input.size;
     250            2 :       inputs.add(input);
     251              :     }
     252              : 
     253              :     /// Outputs
     254            4 :     final (outputLength, outputLengthByteLength) = buffer.bytes.readVarInt(offset);
     255            2 :     offset += outputLengthByteLength;
     256              : 
     257            2 :     final outputs = <BTCOutput>[];
     258              : 
     259            4 :     for (int i = 0; i < outputLength; i++) {
     260            4 :       final output = BTCOutput.fromBuffer(buffer.sublist(offset));
     261            4 :       offset += output.size;
     262            2 :       outputs.add(output);
     263              :     }
     264              : 
     265              :     /// Witness
     266              :     if (isSegwit) {
     267            2 :       List<(Uint8List, BTCInput)> wittnessScripts = [];
     268              : 
     269            4 :       for (final input in inputs) {
     270            4 :         final (emptyScript, emptyScriptLength) = buffer.bytes.readUint8(offset);
     271            2 :         if (emptyScript == 0x00) {
     272            2 :           offset += emptyScriptLength;
     273              :           continue;
     274              :         }
     275              : 
     276            2 :         final (wittnessScript, length) = readScriptWittness(buffer: buffer, offset: offset);
     277            2 :         wittnessScripts.add((wittnessScript, input));
     278            2 :         offset += length;
     279              :       }
     280              : 
     281            4 :       for (final (wittnessScript, input) in wittnessScripts) {
     282            2 :         final index = inputs.indexOf(input);
     283            4 :         inputs[index] = input.addScript(wittnessScript: wittnessScript);
     284              :       }
     285              :     }
     286              : 
     287              :     /// Locktime
     288            4 :     final (lockTime, _) = buffer.bytes.readUint32(offset);
     289              : 
     290            2 :     return BTCRawTransaction(
     291              :       version: version,
     292              :       lockTime: lockTime,
     293              :       inputs: inputs,
     294              :       outputs: outputs,
     295              :     );
     296              :   }
     297              : 
     298            5 :   Uint8List get bytes {
     299           20 :     final inputBuffers = inputs.map((input) => input.bytes);
     300              :     const inputLengthByte = 1;
     301            5 :     final inputsByteLength = inputBuffers.fold(
     302              :       0,
     303           15 :       (prev, buffer) => prev + buffer.length,
     304              :     );
     305              : 
     306           20 :     final outputBuffers = outputs.map((output) => output.bytes);
     307              :     const outputLengthByte = 1;
     308            5 :     final outputsByteLength = outputBuffers.fold(
     309              :       0,
     310           15 :       (prev, buffer) => prev + buffer.length,
     311              :     );
     312              : 
     313              :     var txByteLength =
     314           25 :         4 + inputLengthByte + inputsByteLength + outputLengthByte + outputsByteLength + 4;
     315              : 
     316            5 :     if (hasWitness) {
     317            3 :       txByteLength += 1; // Segwit Flag
     318            3 :       txByteLength += 1; // Segwit Marker
     319              : 
     320            9 :       txByteLength += segwitInputs.fold(
     321              :         0,
     322            3 :         (prev, input) {
     323            6 :           assert(input.isSegwit);
     324            9 :           return prev + input.wittnessScript.length;
     325              :         },
     326              :       );
     327            9 :       txByteLength += nonSegwitInputs.length; // Empty Script
     328              :     }
     329              : 
     330              :     ///
     331              :     /// Construct Buffer
     332              :     ///
     333            5 :     final buffer = Uint8List(txByteLength);
     334              :     var offset = 0;
     335              : 
     336              :     /// Version
     337           20 :     offset += buffer.bytes.writeUint32(offset, version);
     338              : 
     339              :     /// Segwit Flag
     340            5 :     if (hasWitness) {
     341            9 :       offset += buffer.bytes.writeUint8(offset, SEGWIT_MARKER);
     342            9 :       offset += buffer.bytes.writeUint8(offset, SEGWIT_FLAG);
     343              :     }
     344              : 
     345              :     /// Inputs
     346           25 :     offset += buffer.bytes.writeVarInt(offset, inputs.length);
     347           10 :     for (final input in inputs) {
     348           15 :       offset += buffer.writeSlice(offset, input.bytes);
     349              :     }
     350              : 
     351              :     /// Outputs
     352           25 :     offset += buffer.bytes.writeVarInt(offset, outputs.length);
     353           10 :     for (final output in outputs) {
     354           15 :       offset += buffer.writeSlice(offset, output.bytes);
     355              :     }
     356              : 
     357              :     /// Witness
     358            5 :     if (hasWitness)
     359            6 :       for (final input in inputs) {
     360            3 :         if (input.isSegwit) {
     361            9 :           offset += buffer.writeSlice(offset, input.wittnessScript);
     362              :           continue;
     363              :         }
     364              : 
     365            9 :         offset += buffer.bytes.writeUint8(offset, 0x00); // Empty Script
     366              :       }
     367              : 
     368              :     /// Locktime
     369           20 :     offset += buffer.bytes.writeUint32(offset, lockTime);
     370              : 
     371              :     return buffer;
     372              :   }
     373              : 
     374              :   ///
     375              :   /// BIP143 SigHash: https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki
     376              :   ///
     377            3 :   Uint8List bip143sigHash({
     378              :     required int index,
     379              :     required Uint8List prevScriptPubKey,
     380              :     required ElectrumOutput output,
     381              :     required int hashType,
     382              :   }) {
     383              :     ///
     384              :     /// Always use P2PKH in bip143 sigHash
     385              :     ///
     386            3 :     final converter = PublicKeyScriptConverter(prevScriptPubKey);
     387            3 :     final p2pkhScript = converter.p2pkhScript;
     388              : 
     389           12 :     final outputBuffers = outputs.map((output) => output.bytes);
     390            3 :     final txOutSize = outputBuffers.fold(
     391              :       0,
     392            9 :       (prev, element) => prev + element.length,
     393              :     );
     394              : 
     395              :     ///
     396              :     /// Inputs
     397              :     ///
     398           12 :     var tBuffer = Uint8List(36 * inputs.length);
     399              :     var tOffset = 0;
     400            6 :     for (final input in inputs) {
     401            9 :       tOffset += tBuffer.writeSlice(tOffset, input.txid);
     402           12 :       tOffset += tBuffer.bytes.writeUint32(tOffset, input.vout);
     403              :     }
     404              : 
     405            3 :     final hashPrevouts = sha256Sha256Hash(tBuffer);
     406              : 
     407           12 :     tBuffer = Uint8List(4 * inputs.length);
     408              :     tOffset = 0;
     409            6 :     for (final input in inputs) {
     410           12 :       tOffset += tBuffer.bytes.writeUint32(tOffset, input.sequence);
     411              :     }
     412            3 :     final hashSequence = sha256Sha256Hash(tBuffer);
     413              : 
     414              :     /// Outputs
     415              : 
     416            3 :     tBuffer = Uint8List(txOutSize);
     417              :     tOffset = 0;
     418              : 
     419            6 :     for (final output in outputs) {
     420           15 :       tOffset += tBuffer.bytes.writeUint64(tOffset, output.value.toInt());
     421            9 :       tOffset += tBuffer.writeVarSlice(tOffset, output.scriptPubKey);
     422              :     }
     423            3 :     final hashOutputs = sha256Sha256Hash(tBuffer);
     424              : 
     425              :     /// Final Buffer
     426            6 :     final inputToSign = inputs[index];
     427            3 :     final prevScriptPubKeyLength = varSliceSize(p2pkhScript);
     428            6 :     tBuffer = Uint8List(156 + prevScriptPubKeyLength);
     429              :     tOffset = 0;
     430              : 
     431           12 :     tOffset += tBuffer.bytes.writeUint32(tOffset, version);
     432            6 :     tOffset += tBuffer.writeSlice(tOffset, hashPrevouts);
     433            6 :     tOffset += tBuffer.writeSlice(tOffset, hashSequence);
     434              : 
     435            9 :     tOffset += tBuffer.writeSlice(tOffset, inputToSign.txid);
     436           12 :     tOffset += tBuffer.bytes.writeUint32(tOffset, inputToSign.vout);
     437              : 
     438            6 :     tOffset += tBuffer.writeVarSlice(tOffset, p2pkhScript);
     439              : 
     440           15 :     tOffset += tBuffer.bytes.writeUint64(tOffset, output.value.toInt());
     441           12 :     tOffset += tBuffer.bytes.writeUint32(tOffset, inputToSign.sequence);
     442              : 
     443            6 :     tOffset += tBuffer.writeSlice(tOffset, hashOutputs);
     444              : 
     445           12 :     tOffset += tBuffer.bytes.writeUint32(tOffset, lockTime);
     446            9 :     tOffset += tBuffer.bytes.writeUint32(tOffset, hashType);
     447              : 
     448            3 :     final hash = sha256Sha256Hash(tBuffer);
     449              : 
     450              :     return hash;
     451              :   }
     452              : }
     453              : 
     454              : ///
     455              : /// Raw Transaction Implementation for EC8
     456              : ///
     457              : class EC8RawTransaction extends RawTransaction {
     458              :   final List<EC8Input> inputs;
     459              :   final List<EC8Output> outputs;
     460              : 
     461              :   final int validFrom;
     462              :   final int validUntil;
     463              : 
     464            2 :   const EC8RawTransaction({
     465              :     required super.version,
     466              :     required this.inputs,
     467              :     required this.outputs,
     468              :     required this.validFrom,
     469              :     required this.validUntil,
     470              :     super.inputMap,
     471            2 :   }) : super(
     472              :           inputs: inputs,
     473              :           outputs: outputs,
     474              :         );
     475              : 
     476            1 :   String get txid {
     477            1 :     final buffer = bytesForTxId;
     478            1 :     final hash = sha256Sha256Hash(buffer);
     479            2 :     return hash.rev.toHex;
     480              :   }
     481              : 
     482            1 :   @override
     483              :   Uint8List get bytes {
     484            4 :     final inputBuffers = inputs.map((input) => input.bytes);
     485              :     const inputLengthByte = 1;
     486            1 :     final inputsByteLength = inputBuffers.fold(
     487              :       0,
     488            3 :       (prev, buffer) => prev + buffer.length,
     489              :     );
     490              : 
     491            4 :     final outputBuffers = outputs.map((output) => output.bytes);
     492              :     const outputLengthByte = 1;
     493            1 :     final outputsByteLength = outputBuffers.fold(
     494              :       0,
     495            3 :       (prev, buffer) => prev + buffer.length,
     496              :     );
     497              : 
     498            1 :     var txByteLength = 4 +
     499            1 :         inputLengthByte +
     500            1 :         inputsByteLength +
     501            1 :         outputLengthByte +
     502            1 :         outputsByteLength +
     503            1 :         8 +
     504            1 :         4 +
     505            1 :         8 +
     506              :         4;
     507              : 
     508              :     ///
     509              :     /// Construct Buffer
     510              :     ///
     511            1 :     final buffer = Uint8List(txByteLength);
     512              :     var offset = 0;
     513              : 
     514              :     /// Version
     515            4 :     offset += buffer.bytes.writeUint32(offset, version);
     516              : 
     517              :     /// Inputs
     518            5 :     offset += buffer.bytes.writeVarInt(offset, inputs.length);
     519            2 :     for (final input in inputs) {
     520            3 :       offset += buffer.writeSlice(offset, input.bytes);
     521              :     }
     522              : 
     523              :     /// Outputs
     524            5 :     offset += buffer.bytes.writeVarInt(offset, outputs.length);
     525            2 :     for (final output in outputs) {
     526            3 :       offset += buffer.writeSlice(offset, output.bytes);
     527              :     }
     528              : 
     529              :     /// Fee
     530            5 :     offset += buffer.bytes.writeUint64(offset, fee.toInt());
     531              : 
     532              :     /// Weight
     533            5 :     offset += buffer.bytes.writeUint32(offset, weight.toInt());
     534              : 
     535              :     /// ValidFrom
     536            4 :     offset += buffer.bytes.writeUint64(offset, validFrom);
     537              : 
     538              :     /// ValidUntil
     539            4 :     offset += buffer.bytes.writeUint32(offset, validUntil);
     540              : 
     541              :     return buffer;
     542              :   }
     543              : 
     544            1 :   Uint8List get bytesForTxId {
     545            4 :     final inputBuffers = inputs.map((input) => input.bytesForTxId);
     546              :     const inputLengthByte = 1;
     547            1 :     final inputsByteLength = inputBuffers.fold(
     548              :       0,
     549            3 :       (prev, buffer) => prev + buffer.length,
     550              :     );
     551              : 
     552            4 :     final outputBuffers = outputs.map((output) => output.bytesForTxId);
     553              :     const outputLengthByte = 1;
     554            1 :     final outputsByteLength = outputBuffers.fold(
     555              :       0,
     556            3 :       (prev, buffer) => prev + buffer.length,
     557              :     );
     558              : 
     559            1 :     var txByteLength = 4 +
     560            1 :         inputLengthByte +
     561            1 :         inputsByteLength +
     562            1 :         outputLengthByte +
     563            1 :         outputsByteLength +
     564            1 :         8 +
     565            1 :         4 +
     566            1 :         8 +
     567              :         4;
     568              : 
     569              :     ///
     570              :     /// Construct Buffer
     571              :     ///
     572            1 :     final buffer = Uint8List(txByteLength);
     573              :     var offset = 0;
     574              : 
     575              :     /// Version
     576            4 :     offset += buffer.bytes.writeUint32(offset, version);
     577              : 
     578              :     /// Inputs
     579            5 :     offset += buffer.bytes.writeVarInt(offset, inputs.length);
     580            2 :     for (final input in inputs) {
     581            3 :       offset += buffer.writeSlice(offset, input.bytesForTxId);
     582              :     }
     583              : 
     584              :     /// Outputs
     585            5 :     offset += buffer.bytes.writeVarInt(offset, outputs.length);
     586            2 :     for (final output in outputs) {
     587            3 :       offset += buffer.writeSlice(offset, output.bytesForTxId);
     588              :     }
     589              : 
     590              :     /// Fee
     591            5 :     offset += buffer.bytes.writeUint64(offset, fee.toInt());
     592              : 
     593              :     /// Weight
     594            3 :     offset += buffer.bytes.writeUint32(offset, 0);
     595              : 
     596              :     /// ValidFrom
     597            4 :     offset += buffer.bytes.writeUint64(offset, validFrom);
     598              : 
     599              :     /// ValidUntil
     600            4 :     offset += buffer.bytes.writeUint32(offset, validUntil);
     601              : 
     602              :     return buffer;
     603              :   }
     604              : 
     605              :   ///
     606              :   /// Doesnt include weight
     607              :   ///
     608            1 :   Uint8List get bytesForSigning {
     609              :     /// Double SHA256 Hash of all inputs
     610            2 :     final inputBuffers = inputs.map(
     611            2 :       (input) => input.bytesForSigning(
     612              :         withWeight: true,
     613              :         withScript: false,
     614              :       ),
     615              :     );
     616            1 :     final combinedInputBuffers = inputBuffers.fold(
     617            1 :       Uint8List(0),
     618            3 :       (prev, buffer) => Uint8List.fromList(prev + buffer),
     619              :     );
     620            1 :     final hashInputs = sha256Sha256Hash(combinedInputBuffers);
     621              : 
     622              :     /// Double SHA256 Hash of all outputs
     623            4 :     final outputBuffers = outputs.map((output) => output.bytes);
     624            1 :     final combinedOutputBuffers = outputBuffers.fold(
     625            1 :       Uint8List(0),
     626            3 :       (prev, buffer) => Uint8List.fromList(prev + buffer),
     627              :     );
     628            1 :     final hashOutputs = sha256Sha256Hash(combinedOutputBuffers);
     629              : 
     630              :     ///
     631              :     /// Input to be signed
     632              :     ///
     633              : 
     634              :     /// Input to be signed has a scriptSig all other inputs have empty scriptSigs
     635            2 :     final input = inputs.singleWhereOrNull(
     636            4 :       (input) => input.scriptSig.length != 0,
     637              :     );
     638              :     if (input == null) {
     639            0 :       throw Exception('No input to be signed');
     640              :     }
     641              : 
     642            1 :     final inputBytes = input.bytesForSigning(
     643              :       withWeight: false,
     644              :       withScript: true,
     645              :     );
     646              : 
     647              :     ///
     648              :     /// Construct Buffer
     649              :     ///
     650            9 :     final txByteLength = 4 + hashInputs.length + hashOutputs.length + inputBytes.length + 8 + 8 + 4;
     651            1 :     final buffer = Uint8List(txByteLength);
     652              :     var offset = 0;
     653              : 
     654              :     /// Version
     655            4 :     offset += buffer.bytes.writeUint32(offset, version);
     656              : 
     657              :     /// HashInputs
     658            2 :     offset += buffer.writeSlice(offset, hashInputs);
     659              : 
     660              :     /// Input to be signed
     661            2 :     offset += buffer.writeSlice(
     662              :       offset,
     663              :       inputBytes,
     664              :     );
     665              : 
     666              :     /// HashOutputs
     667            2 :     offset += buffer.writeSlice(offset, hashOutputs);
     668              : 
     669              :     /// Fee
     670            5 :     offset += buffer.bytes.writeUint64(offset, fee.toInt());
     671              : 
     672              :     /// ValidFrom
     673            4 :     offset += buffer.bytes.writeUint64(offset, validFrom);
     674              : 
     675              :     /// ValidUntil
     676            4 :     offset += buffer.bytes.writeUint32(offset, validUntil);
     677              : 
     678              :     return buffer;
     679              :   }
     680              : 
     681            1 :   EC8RawTransaction createCopy() {
     682            1 :     return EC8RawTransaction(
     683            1 :       version: version,
     684            1 :       inputs: inputs,
     685            1 :       outputs: outputs,
     686            1 :       validFrom: validFrom,
     687            1 :       validUntil: validUntil,
     688              :     );
     689              :   }
     690              : 
     691            1 :   EC8RawTransaction _addInputs(
     692              :     List<Input>? inputs,
     693              :   ) {
     694            2 :     final signedInputs = inputs?.whereType<EC8Input>().toList() ?? this.inputs;
     695              : 
     696            1 :     return EC8RawTransaction(
     697            1 :       version: version,
     698              :       inputs: signedInputs,
     699            1 :       outputs: outputs,
     700            1 :       validFrom: validFrom,
     701            1 :       validUntil: validUntil,
     702              :     );
     703              :   }
     704              : 
     705            2 :   factory EC8RawTransaction.fromHex(String hex) {
     706            2 :     final buffer = hex.hexToBytes;
     707              : 
     708              :     var offset = 0;
     709              : 
     710              :     /// Version
     711            4 :     final (version, length) = buffer.bytes.readUint32(offset);
     712            2 :     offset += length;
     713              : 
     714              :     /// Inputs
     715            4 :     final (inputLength, inputLengthByteLength) = buffer.bytes.readVarInt(offset);
     716            2 :     offset += inputLengthByteLength;
     717              : 
     718            2 :     final inputs = <EC8Input>[];
     719            4 :     for (int i = 0; i < inputLength; i++) {
     720            4 :       final input = EC8Input.fromBuffer(buffer.sublist(offset));
     721            4 :       offset += input.size;
     722            2 :       inputs.add(input);
     723              :     }
     724              : 
     725              :     /// Outputs
     726            4 :     final (outputLength, outputLengthByteLength) = buffer.bytes.readVarInt(offset);
     727            2 :     offset += outputLengthByteLength;
     728              : 
     729            2 :     final outputs = <EC8Output>[];
     730              : 
     731            4 :     for (int i = 0; i < outputLength; i++) {
     732            4 :       final output = EC8Output.fromBuffer(buffer.sublist(offset));
     733            4 :       offset += output.size;
     734            2 :       outputs.add(output);
     735              :     }
     736              : 
     737              :     /// Fee (is ignored since it is calculated from inputs and outputs)
     738            4 :     final (_, feeOffset) = buffer.bytes.readUint64(offset);
     739            2 :     offset += feeOffset;
     740              : 
     741              :     /// Weight (is ignored since it is calculated)
     742            4 :     final (_, weightOffset) = buffer.bytes.readUint32(offset);
     743            2 :     offset += weightOffset;
     744              : 
     745              :     /// ValidFrom
     746            4 :     final (validFrom, validFromOffset) = buffer.bytes.readUint64(offset);
     747            2 :     offset += validFromOffset;
     748              : 
     749              :     /// ValidUntil
     750            4 :     final (validUntil, validUntilOffset) = buffer.bytes.readUint32(offset);
     751            2 :     offset += validUntilOffset;
     752              : 
     753            2 :     return EC8RawTransaction(
     754              :       version: version,
     755              :       inputs: inputs,
     756              :       outputs: outputs,
     757              :       validFrom: validFrom,
     758              :       validUntil: validUntil,
     759              :     );
     760              :   }
     761              : }
        

Generated by: LCOV version 2.0-1