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

Generated by: LCOV version 2.0-1