PHP WebShell

Текущая директория: /opt/BitGoJS/modules/sdk-coin-ada/src/lib

Просмотр файла: utils.ts

import { AddressFormat, BaseUtils, InvalidAddressError } from '@bitgo/sdk-core';
import {
  BaseAddress,
  PublicKey,
  Ed25519Signature,
  NetworkInfo,
  Credential,
  RewardAddress,
  Transaction as CardanoTransaction,
  DRep,
  Ed25519KeyHash,
  ScriptHash,
  DRepKind,
  Address,
  EnterpriseAddress,
  PointerAddress,
  ByronAddress,
} from '@emurgo/cardano-serialization-lib-nodejs';
import { KeyPair } from './keyPair';
import { bech32 } from 'bech32';
import bs58 from 'bs58';
import cbor from 'cbor';

export const MIN_ADA_FOR_ONE_ASSET = '1500000';
export const VOTE_ALWAYS_ABSTAIN = 'always-abstain';
export const VOTE_ALWAYS_NO_CONFIDENCE = 'always-no-confidence';

export class Utils implements BaseUtils {
  createBaseAddressWithStakeAndPaymentKey(
    stakeKeyPair: KeyPair,
    paymentKeyPair: KeyPair,
    network: AddressFormat
  ): string {
    let baseAddr;
    if (network === AddressFormat.mainnet) {
      // 1. create stake pubKey
      const key = stakeKeyPair.getKeys().pub;

      const stakePub = PublicKey.from_bytes(Buffer.from(key, 'hex'));
      // 2. create payment pubKey
      const paymentPub = PublicKey.from_bytes(Buffer.from(paymentKeyPair.getKeys().pub, 'hex'));
      // 3. create full base address for staking
      baseAddr = BaseAddress.new(
        NetworkInfo.mainnet().network_id(),
        Credential.from_keyhash(paymentPub.hash()),
        Credential.from_keyhash(stakePub.hash())
      );
      return baseAddr.to_address().to_bech32();
    } else if (network === AddressFormat.testnet) {
      // 1. create stake pubKey
      const stakePub = PublicKey.from_bytes(Buffer.from(stakeKeyPair.getKeys().pub, 'hex'));
      // 2. create payment pubKey
      const paymentPub = PublicKey.from_bytes(Buffer.from(paymentKeyPair.getKeys().pub, 'hex'));
      // 3. create full base address for staking
      const baseAddr = BaseAddress.new(
        NetworkInfo.testnet_preprod().network_id(),
        Credential.from_keyhash(paymentPub.hash()),
        Credential.from_keyhash(stakePub.hash())
      );
      return baseAddr.to_address().to_bech32();
    } else {
      throw new Error('Improper Network Type!');
    }
  }

  validateBlake2b(hash: string): boolean {
    if (!hash) {
      return false;
    }
    if (hash.length !== 64) {
      return false;
    }
    return hash.match(/^[a-zA-Z0-9]+$/) !== null;
  }

  getRewardAddress(stakingPubKey: string, coinName: string): string {
    const stakePub = PublicKey.from_bytes(Buffer.from(stakingPubKey, 'hex'));
    let rewardAddress;
    if (coinName === 'ada') {
      rewardAddress = RewardAddress.new(NetworkInfo.mainnet().network_id(), Credential.from_keyhash(stakePub.hash()));
    } else {
      rewardAddress = RewardAddress.new(
        NetworkInfo.testnet_preprod().network_id(),
        Credential.from_keyhash(stakePub.hash())
      );
    }
    return rewardAddress.to_address().to_bech32();
  }

  isValidDRepId(dRepId: string): boolean {
    try {
      this.getDRepFromDRepId(dRepId);
      return true;
    } catch (err) {
      return false;
    }
  }

  getDRepFromDRepId(dRepId: string): DRep {
    switch (dRepId) {
      case 'always-abstain':
        return DRep.new_always_abstain();
      case 'always-no-confidence':
        return DRep.new_always_no_confidence();
      default:
        try {
          // for parsing CIP-105 standard DRep ID
          return DRep.from_bech32(dRepId);
        } catch (err) {
          // for parsing CIP-129 standard DRep ID
          // https://cips.cardano.org/cip/CIP-0129
          const decodedBech32 = bech32.decode(dRepId);
          const decodedBytes = Buffer.from(bech32.fromWords(decodedBech32.words));
          const header = decodedBytes[0];
          const keyBytes = decodedBytes.subarray(1);

          const keyType = (header & 0xf0) >> 4;
          const credentialType = header & 0x0f;

          if (keyType !== 0x02) {
            throw new Error('Invalid key type for DRep');
          }

          switch (credentialType) {
            case 0x02:
              const ed25519KeyHash = Ed25519KeyHash.from_bytes(keyBytes);
              return DRep.new_key_hash(ed25519KeyHash);
            case 0x03:
              const scriptHash = ScriptHash.from_bytes(keyBytes);
              return DRep.new_script_hash(scriptHash);
            default:
              throw new Error('Invalid credential type for DRep');
          }
        }
    }
  }

  getDRepIdFromDRep(dRep: DRep): string {
    switch (dRep.kind()) {
      case DRepKind.AlwaysAbstain:
        return VOTE_ALWAYS_ABSTAIN;
      case DRepKind.AlwaysNoConfidence:
        return VOTE_ALWAYS_NO_CONFIDENCE;
      default:
        return dRep.to_bech32();
    }
  }

  /** @inheritdoc */
  // this will validate both stake and payment addresses
  isValidAddress(address: string): boolean {
    const bech32PrefixList = ['addr', 'addr_test', 'stake', 'stake_test'];
    const BASE_ADDR_LEN = 92;
    const REWARD_AND_ENTERPRISE_ADDR_LEN = 47;
    const POINTER_ADDR_LEN = 52;
    const VALIDATOR_ADDR_LEN = 56;

    //Check for Shelley-era (Bech32) addresses
    if (new RegExp(`^(${bech32PrefixList.join('|')})`).test(address)) {
      try {
        const decodedBech = bech32.decode(address, 108);
        const wordLength = decodedBech.words.length;
        if (
          bech32PrefixList.includes(decodedBech.prefix) &&
          (wordLength === BASE_ADDR_LEN ||
            wordLength === REWARD_AND_ENTERPRISE_ADDR_LEN ||
            wordLength === POINTER_ADDR_LEN)
        ) {
          return true;
        }
      } catch (e) {
        console.log(`Address: ${address} failed Bech32 test with error: ${e}`);
      }
    }

    //Check for Validator addresses
    if (new RegExp(`^(?!pool)[a-z0-9]{${VALIDATOR_ADDR_LEN}}$`).test(address)) {
      return true;
    }

    //Check for Byron-era address
    try {
      // Reject Daedalus wallet addresses (Byron-era addresses starting with "DdzFF")
      if (address.startsWith('DdzFF')) {
        console.log(`Rejecting Daedalus wallet address: ${address}`);
        return false;
      }

      const decoded = bs58.decode(address);
      const cborData = cbor.decodeFirstSync(decoded);
      return Array.isArray(cborData) && cborData.length >= 2;
    } catch (e) {
      console.log(`Address: ${address} failed Byron test with error: ${e}`);
      console.log(e.stack);
    }

    return false;
  }

  /** @inheritdoc */
  isValidBlockId(hash: string): boolean {
    return this.validateBlake2b(hash);
  }

  /** @inheritdoc */
  isValidPrivateKey(key: string): boolean {
    // this will return true for both extended and non-extended ED25519 keys
    return this.isValidKey(key);
  }

  isValidKey(key: string): boolean {
    try {
      new KeyPair({ prv: key });
      return true;
    } catch {
      return false;
    }
  }

  /** @inheritdoc */
  isValidPublicKey(pubKey: string): boolean {
    try {
      new KeyPair({ pub: pubKey });
      return true;
    } catch {
      return false;
    }
  }

  /** @inheritdoc */
  isValidSignature(signature: string): boolean {
    try {
      Ed25519Signature.from_hex(signature);
      return true;
    } catch (err) {
      return false;
    }
  }

  /** @inheritdoc */
  isValidTransactionId(txId: string): boolean {
    return this.validateBlake2b(txId);
  }

  /**
   * Get the transaction body from a serialized transaction
   * @param {string} serializedTx - serialized transaction in hex or base64 format
   * @returns {string} transaction body in hex format
   */
  getTransactionBody(serializedTx: string): string {
    const HEX_REGEX = /^[0-9a-fA-F]+$/;
    const bufferRawTransaction = HEX_REGEX.test(serializedTx)
      ? Buffer.from(serializedTx, 'hex')
      : Buffer.from(serializedTx, 'base64');
    return Buffer.from(CardanoTransaction.from_bytes(bufferRawTransaction).body().to_bytes()).toString('hex');
  }

  /**
   * Decode wallet address from string.
   * Attempts to decode as Shelley (bech32) first, then Byron (base58).
   * @param {string} address - Valid Byron or Shelley-era address.
   * @returns {Address} - Valid address object.
   * @throws {InvalidAddressError} If the address is neither valid Shelley nor Byron.
   */
  getWalletAddress(address: string): Address {
    if (!address || typeof address !== 'string') {
      throw new InvalidAddressError('Provided address is not a valid string');
    }

    // Try decoding as a Shelley (bech32) address first
    try {
      return Address.from_bech32(address);
    } catch (e) {
      console.error(`Could not decode shelly address from string '${address}'`);
    }

    // Try decoding as a Byron (base58) address later
    try {
      // Reject Daedalus wallet addresses (Byron-era addresses starting with "DdzFF")
      if (address.startsWith('DdzFF')) {
        throw new InvalidAddressError('Provided string is a Daedalus address');
      }

      return ByronAddress.from_base58(address).to_address();
    } catch (e) {
      console.error(`Could not decode byron address from string '${address}'`);
    }
    throw new InvalidAddressError('Provided string is not a valid Shelley or Byron address');
  }

  /**
   * Decode address string from Address object.
   * Attempts to decode as Shelley (bech32) first, then Byron (base58).
   * @param {Address} address - Valid Address object
   * @returns {string} - Valid Byron or Shelley-era address string.
   * @throws {InvalidAddressError} If the Address object is neither valid Shelley nor Byron.
   */
  getAddressString(address: Address): string {
    // Check all Shelley address types
    if (
      BaseAddress.from_address(address) ||
      EnterpriseAddress.from_address(address) ||
      RewardAddress.from_address(address) ||
      PointerAddress.from_address(address)
    ) {
      return address.to_bech32();
    }

    const byronAddress = ByronAddress.from_address(address);
    // Reject Daedalus wallet addresses (Byron-era addresses starting with "DdzFF")
    if (byronAddress) {
      if (byronAddress.to_base58().startsWith('DdzFF')) {
        throw new InvalidAddressError('Provided address is a Daedalus address');
      }
      return byronAddress.to_base58();
    }

    // If neither, it's invalid
    throw new InvalidAddressError('Provided Address is not a valid Shelley or Byron address');
  }
}

const utils = new Utils();

export default utils;

Выполнить команду


Для локальной разработки. Не используйте в интернете!