LCOV - code coverage report
Current view: top level - crypto/utxo/utils - proof_of_payment.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 98.1 % 54 53
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/raw_transaction/output.dart';
       5              : import 'package:walletkit_dart/src/crypto/utxo/entities/op_codes.dart';
       6              : import 'package:walletkit_dart/src/crypto/utxo/utils/pubkey_to_address.dart';
       7              : import 'package:walletkit_dart/src/crypto/utxo/repositories/electrum_json_rpc_client.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              : import 'ecurve.dart' as ecc;
      12              : 
      13              : class POPResult {
      14              :   final Uint8List uPoPHash;
      15              :   final BTCRawTransaction upopTx;
      16              :   final String originalTxId;
      17              : 
      18              :   /// Signatures of uPoPHash for each input used in the to be proven transaction
      19              :   final List<Uint8List> pops;
      20              : 
      21            1 :   const POPResult(
      22              :     this.uPoPHash,
      23              :     this.upopTx,
      24              :     this.pops,
      25              :     this.originalTxId,
      26              :   );
      27              : 
      28            1 :   bool verifiyPop(int index, Uint8List publicKey) {
      29            4 :     assert(index < pops.length);
      30            4 :     return ecc.verify(uPoPHash, publicKey, pops[index]);
      31              :   }
      32              : 
      33            1 :   Map<String, dynamic> toJson() {
      34            1 :     return {
      35            1 :       "txId": originalTxId,
      36            2 :       "uPoP": upopTx.asHex,
      37            2 :       "uPoPHash": uPoPHash.toHex,
      38            1 :       "pops": [
      39            2 :         for (final pop in pops) pop.toHex,
      40              :       ],
      41              :     };
      42              :   }
      43              : }
      44              : 
      45              : ///
      46              : /// Proof of Payment using BIP120: https://bips.xyz/120
      47              : ///
      48            1 : Future<POPResult> proofOfPayment({
      49              :   required String txid,
      50              :   required String nonce,
      51              :   required List<NodeWithAddress> nodes,
      52              :   required Uint8List seed,
      53              :   required UTXONetworkType networkType,
      54              : }) async {
      55            1 :   final isEc8 = networkType == EurocoinNetwork;
      56            1 :   final nonceBytes = nonce.hexToBytesWithPrefix;
      57              : 
      58              :   ///
      59              :   /// Fetch to be proven Tx
      60              :   ///
      61            1 :   final tbProvenTxSerialized = await fetchRawTxByHash(txid, networkType);
      62              :   final tbProvenTx = isEc8
      63            1 :       ? EC8RawTransaction.fromHex(tbProvenTxSerialized)
      64            1 :       : BTCRawTransaction.fromHex(tbProvenTxSerialized);
      65              : 
      66              :   ///
      67              :   /// Get Nodes for Inputs
      68              :   ///
      69              :   ///
      70            1 :   final listEquality = const ListEquality().equals;
      71            1 :   final usedNodes = [
      72            1 :     for (final input in tbProvenTx.inputs)
      73            1 :       () {
      74            1 :         final node = nodes.firstWhereOrNull(
      75            2 :           (node) => listEquality(
      76            2 :             node.publicKey.hexToBytes,
      77            1 :             input.publicKeyFromSig,
      78              :           ),
      79              :         );
      80              :         if (node == null) return null;
      81              :         return node;
      82            1 :       }.call()
      83            2 :   ].whereType<NodeWithAddress>().toList();
      84              : 
      85              :   assert(
      86            5 :     tbProvenTx.inputs.length == usedNodes.length,
      87              :     "Could not find the Nodes for the given transaction.",
      88              :   );
      89              : 
      90              :   ///
      91              :   /// Create Pop Output
      92              :   ///
      93            6 :   final pop_output_script = Uint8List(1 + 2 + 32 + nonceBytes.length + 1);
      94              :   var offset = 0;
      95            3 :   offset += pop_output_script.bytes.writeUint8(offset, OP_RETURN);
      96            3 :   offset += pop_output_script.bytes.writeUint16(offset, 0x01); // POP Version
      97            3 :   offset += pop_output_script.writeSlice(offset, txid.hexToBytes);
      98            2 :   offset += pop_output_script.writeVarSlice(offset, nonceBytes);
      99              : 
     100            1 :   final pop_output = BTCOutput(
     101            1 :     value: 0.toBI,
     102              :     scriptPubKey: pop_output_script,
     103              :   );
     104              : 
     105              :   ///
     106              :   /// Adjust Inputs (Set Sequence to 0x00000000)
     107              :   ///
     108            3 :   final pop_inputs = tbProvenTx.inputs.map((input) {
     109            1 :     return input.changeSequence(0x00000000);
     110            1 :   }).toList();
     111              : 
     112              :   ///
     113              :   /// Create UPoP
     114              :   ///
     115            1 :   final uPopTx = BTCRawTransaction(
     116            1 :     version: tbProvenTx.version,
     117              :     lockTime: 499999999,
     118              :     inputs: pop_inputs,
     119            1 :     outputs: [pop_output],
     120              :   );
     121            1 :   final uPoPSerialized = uPopTx.bytes;
     122              : 
     123              :   ///
     124              :   /// Create Pop Signature
     125              :   ///
     126            1 :   final uPoPHash = sha256Sha256Hash(uPoPSerialized);
     127              : 
     128            3 :   if (usedNodes.any((node) => node.walletPurpose == null)) {
     129            0 :     throw Exception("WalletPurpose is required for all nodes.");
     130              :   }
     131              : 
     132            1 :   final bip32Nodes = [
     133            1 :     for (final node in usedNodes)
     134            1 :       deriveChildNodeFromPath(
     135              :         seed: seed,
     136              :         networkType: networkType,
     137            1 :         childDerivationPath: node.derivationPath,
     138            1 :         walletPath: HDWalletPath.fromPurpose(
     139            1 :           node.walletPurpose!,
     140              :           networkType,
     141              :         ), // TODO: Store HDWalletPath better
     142              :       ),
     143              :   ];
     144              : 
     145            1 :   final signatures = [
     146            3 :     for (final node in bip32Nodes) (node.sign(uPoPHash) as Uint8List),
     147              :   ];
     148              : 
     149            1 :   return POPResult(uPoPHash, uPopTx, signatures, txid);
     150              : }
        

Generated by: LCOV version 2.0-1