LCOV - code coverage report
Current view: top level - crypto/utxo/entities/payments - p2h.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 87.7 % 114 100
Test Date: 2025-07-02 01:23:33 Functions: - 0 0

            Line data    Source code
       1              : import 'dart:typed_data';
       2              : import 'package:bs58check/bs58check.dart' as bs58check;
       3              : import 'package:convert/convert.dart';
       4              : import 'package:dart_bech32/dart_bech32.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/utils/base32.dart';
       8              : import 'package:walletkit_dart/walletkit_dart.dart';
       9              : 
      10              : class P2Hash {
      11              :   final String address;
      12              : 
      13           11 :   const P2Hash(this.address);
      14              : 
      15           45 :   String get publicKeyScriptHash => sha256Hash(publicKeyScript).rev.toHex;
      16              : 
      17           11 :   Uint8List get publicKeyScript {
      18           22 :     if (address.startsWith(P2PKH_PREFIX) ||
      19           22 :         address.startsWith(P2PKH_PREFIX_LTC) ||
      20           22 :         address.startsWith(P2PKH_PREFIX_ZENIQ) ||
      21           18 :         address.startsWith(P2PKH_PREFIX_EC8)) {
      22           10 :       return p2pkhScript;
      23              :     }
      24              : 
      25           32 :     if (address.startsWith(P2SH_PREFIX) || address.startsWith(P2SH_PREFIX_LTC)) {
      26            2 :       return p2shScript;
      27              :     }
      28              : 
      29           30 :     if (address.startsWith(P2WPKH_PREFIX_BTC) || address.startsWith(P2WPKH_PREFIX_LTC)) {
      30            7 :       return p2wpkhScript;
      31              :     }
      32              : 
      33              :     /// Remove the prefix
      34            8 :     if (address.startsWith("bitcoincash:")) {
      35            4 :       return p2pkhScriptBCH;
      36              :     }
      37              : 
      38            3 :     throw UnsupportedError("Address type not supported: $address");
      39              :   }
      40              : 
      41              :   ///
      42              :   /// P2PKH
      43              :   ///
      44              : 
      45           10 :   Uint8List get p2pkhScript {
      46           30 :     final decodedHex = bs58check.decode(address).toHex;
      47           10 :     final pubKeyHash = decodedHex.substring(2);
      48              : 
      49           20 :     if (pubKeyHash.length != 40) {
      50            0 :       throw WKFailure("wrong pubKeyHash length");
      51              :     }
      52              :     // see https://en.bitcoinwiki.org/wiki/Pay-to-Pubkey_Hash for P2PKH
      53              :     final scriptP2PKH =
      54           10 :         "${OPCODE.OP_DUP}${OPCODE.OP_HASH160}$PUBKEY_SCRIPT_HASH_LENGTH_HEX$pubKeyHash${OPCODE.OP_EQUALVERIFY}${OPCODE.OP_CHECKSIG}";
      55              : 
      56           10 :     return scriptP2PKH.hexToBytes;
      57              :   }
      58              : 
      59              :   ///
      60              :   /// P2WPKH
      61              :   ///
      62              : 
      63            7 :   Uint8List get p2wpkhScript {
      64           14 :     final decoded = bech32.decode(address);
      65            7 :     var words = decoded.words;
      66              :     // Remove the witness version
      67            7 :     words = words.sublist(1);
      68              :     // Convert 5-bit words to 8-bit
      69              :     Uint8List convertedWords;
      70              :     try {
      71            7 :       convertedWords = bech32.fromWords(words);
      72              :     } catch (e) {
      73              :       convertedWords = words;
      74              :     }
      75              : 
      76              :     final scriptPubKeyHash =
      77           14 :         "${OPCODE.OP_0}$PUBKEY_SCRIPT_HASH_LENGTH_HEX${hex.encode(convertedWords)}";
      78              : 
      79            7 :     return scriptPubKeyHash.hexToBytes;
      80              :   }
      81              : 
      82              :   ///
      83              :   /// P2WPKH BCH
      84              :   ///
      85            4 :   Uint8List get p2pkhScriptBCH {
      86            8 :     final _address = address.substring(12); // remove "bitcoincash:"
      87              : 
      88            8 :     final payload = Base32().decode(_address);
      89              : 
      90           16 :     final payloadData = bech32.fromWords(payload.sublist(0, payload.length - 8));
      91              : 
      92            4 :     final version = payloadData[0];
      93            4 :     final pubKeyHash = payloadData.sublist(1);
      94              : 
      95            8 :     assert(version == 0);
      96           12 :     assert(pubKeyHash.length == 20);
      97              : 
      98            4 :     return [
      99            4 :       OPCODE.OP_DUP.hex,
     100            4 :       OPCODE.OP_HASH160.hex,
     101            4 :       pubKeyHash.length,
     102            4 :       ...pubKeyHash,
     103            4 :       OPCODE.OP_EQUALVERIFY.hex,
     104            4 :       OPCODE.OP_CHECKSIG.hex,
     105            4 :     ].toUint8List;
     106              :   }
     107              : 
     108              :   ///
     109              :   /// P2SH
     110              :   ///
     111              : 
     112            2 :   Uint8List get p2shScript {
     113              :     // Decode the Base58Check encoded P2SH address and extract the script hash
     114            6 :     final scriptHash = bs58check.decode(address).sublist(1);
     115              : 
     116            2 :     return [
     117            2 :       OPCODE.OP_HASH160.hex,
     118            2 :       scriptHash.length,
     119            2 :       ...scriptHash,
     120            2 :       OPCODE.OP_EQUAL.hex,
     121            2 :     ].toUint8List;
     122              :   }
     123              : 
     124              :   ///
     125              :   /// Utility functions
     126              :   ///
     127            0 :   static Uint8List toP2PKHScript(Uint8List segWitScript) {
     128            0 :     final pubkeyhash = segWitScript.sublist(2);
     129              : 
     130            0 :     return [
     131            0 :       OPCODE.OP_DUP.hex,
     132            0 :       OPCODE.OP_HASH160.hex,
     133            0 :       pubkeyhash.length,
     134            0 :       ...pubkeyhash,
     135            0 :       OPCODE.OP_EQUALVERIFY.hex,
     136            0 :       OPCODE.OP_CHECKSIG.hex,
     137            0 :     ].toUint8List;
     138              :   }
     139              : }
     140              : 
     141              : ///
     142              : /// Returns the address from the locking script of a transaction output.
     143              : ///
     144            8 : String getAddressFromLockingScript(
     145              :   ElectrumScriptPubKey scriptPubKey,
     146              :   UTXONetworkType type, {
     147              :   AddressType? addressType,
     148              : }) {
     149            8 :   final (pubKeyHash, walletType) = getPublicKeyFromLockingScript(
     150              :     scriptPubKey,
     151              :     type,
     152              :   );
     153              : 
     154              :   return switch (walletType) {
     155           17 :     HDWalletPurpose.BIP44 when addressType == AddressType.cashaddr => bchAddrEncode(
     156            1 :         hrp: type.bech32,
     157              :         data: pubKeyHash,
     158            1 :         witnessVersion: type.pubKeyHashPrefix,
     159              :       ),
     160           14 :     HDWalletPurpose.BIP44 => pubKeyHashToLegacyAddress(pubKeyHash, type.pubKeyHashPrefix),
     161           12 :     HDWalletPurpose.BIP49 => pubKeyHashToP2SHAddress(pubKeyHash, type.scriptHashPrefix),
     162            4 :     HDWalletPurpose.BIP84 =>
     163           12 :       pubKeyHashToSegwitAddress(pubKeyHash, type.bech32, type.pubKeyHashPrefix),
     164            0 :     _ => throw UnsupportedError("Address type not supported: $pubKeyHash")
     165              :   };
     166              : }
     167              : 
     168            8 : (Uint8List, HDWalletPurpose) getPublicKeyFromLockingScript(
     169              :   ElectrumScriptPubKey scriptPubKey,
     170              :   UTXONetworkType type,
     171              : ) {
     172            8 :   final hexKey = scriptPubKey.hexString;
     173              : 
     174              :   ///
     175              :   /// P2PKH
     176              :   ///
     177           32 :   if (hexKey.startsWith(p2pkhPrefix) && hexKey.endsWith(p2pkhPostfix)) {
     178            8 :     final pubKeyHashHex = hexKey.substring(
     179           16 :       p2pkhPrefix.length,
     180           32 :       hexKey.length - p2pkhPostfix.length,
     181              :     );
     182           16 :     final pubKeyHash = Uint8List.fromList(hex.decode(pubKeyHashHex));
     183              :     return (pubKeyHash, HDWalletPurpose.BIP44);
     184              :   }
     185              : 
     186              :   ///
     187              :   /// P2SH
     188              :   ///
     189           16 :   if (hexKey.startsWith(p2shPrefix) && hexKey.endsWith(p2shPostfix)) {
     190            4 :     final pubKeyHashHex = hexKey.substring(
     191            8 :       p2shPrefix.length,
     192           16 :       hexKey.length - p2shPostfix.length,
     193              :     );
     194            8 :     final pubKeyHash = Uint8List.fromList(hex.decode(pubKeyHashHex));
     195              :     return (pubKeyHash, HDWalletPurpose.BIP49);
     196              :   }
     197              : 
     198              :   ///
     199              :   /// P2WPKH
     200              :   ///
     201            8 :   if (hexKey.startsWith(p2wpkhPrefix)) {
     202            4 :     final pubKeyHashHex = hexKey.substring(
     203            8 :       p2wpkhPrefix.length,
     204            4 :       hexKey.length,
     205              :     );
     206            8 :     final pubKeyHash = Uint8List.fromList(hex.decode(pubKeyHashHex));
     207              :     return (pubKeyHash, HDWalletPurpose.BIP84);
     208              :   }
     209              : 
     210            2 :   throw UnsupportedError("Address type not supported: $hexKey");
     211              : }
     212              : 
     213              : ///
     214              : /// Returns the address from the unlocking script of a transaction input. This only works for P2PKH and P2WPKH inputs.
     215              : ///
     216            8 : String getAddressFromInput(
     217              :   UTXONetworkType type,
     218              :   ElectrumInput input, {
     219              :   AddressType? addressType,
     220              : }) {
     221            8 :   final (publicKey, pubKeyAddressType) = getPubKeyFromInput(input);
     222              : 
     223              :   ///
     224              :   /// Allow for overriding the address type (used for BCH)
     225              :   ///
     226              :   final _addressType = switch (addressType) {
     227            9 :     AddressType.cashaddr when pubKeyAddressType == AddressType.legacy => AddressType.cashaddr,
     228              :     _ => pubKeyAddressType
     229              :   };
     230              : 
     231            8 :   return pubKeyToAddress(publicKey, _addressType, type);
     232              : }
     233              : 
     234              : ///
     235              : /// Returns the PublicKey from the unlocking script of a transaction input. This only works for P2PKH and P2WPKH inputs.
     236              : ///
     237            8 : (Uint8List, AddressType) getPubKeyFromInput(
     238              :   ElectrumInput input,
     239              : ) {
     240            8 :   final hexSig = input.scriptSig;
     241              : 
     242              :   ///
     243              :   /// Use ScriptSig (P2PKH)
     244              :   ///
     245            8 :   if (hexSig != null && hexSig.isNotEmpty) {
     246           16 :     if (hexSig.length < 68) {
     247            8 :       throw UnsupportedError("Address type not supported: $hexSig");
     248              :     }
     249           24 :     final redeemScriptHex = hexSig.substring(hexSig.length - 68);
     250            8 :     if (!redeemScriptHex.startsWith("21")) {
     251              :       // expect varInt 0x21 -> 33 byte pubkey
     252            2 :       throw UnsupportedError("Address type not supported: $hexSig");
     253              :     }
     254            8 :     final pubKeyHex = redeemScriptHex.substring(2);
     255           16 :     if (pubKeyHex.length != 66) {
     256            0 :       throw UnsupportedError("Address type not supported: $hexSig");
     257              :     }
     258            8 :     return (pubKeyHex.hexToBytes, AddressType.legacy);
     259              :   }
     260              : 
     261              :   ///
     262              :   /// Use ScriptWitness (P2WPKH)
     263              :   ///
     264            4 :   final witnessData = input.txinwitness;
     265            4 :   if (witnessData != null && witnessData.isNotEmpty) {
     266              :     // In P2WPKH witness data, the second item is the pubkey. It should start with either "02" or "03" (compressed pubkey).
     267            4 :     final pubKeyHex = witnessData[1];
     268              : 
     269            8 :     if (pubKeyHex.length != 66) {
     270            0 :       throw UnsupportedError("Address type not supported: $witnessData");
     271              :     }
     272              : 
     273            4 :     return (pubKeyHex.hexToBytes, AddressType.segwit);
     274              :   }
     275              : 
     276            2 :   throw UnsupportedError("Address type not supported: $hexSig");
     277              : }
        

Generated by: LCOV version 2.0-1