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

Generated by: LCOV version 2.0-1