PHP WebShell

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

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

import { DotAssetTypes, BaseUtils, DotAddressFormat, isBase58, isValidEd25519PublicKey, Seed } from '@bitgo/sdk-core';
import { BaseCoin as CoinConfig, DotNetwork } from '@bitgo/statics';
import { decodeAddress, encodeAddress, Keyring } from '@polkadot/keyring';
import { decodePair } from '@polkadot/keyring/pair/decode';
import { KeyringPair } from '@polkadot/keyring/types';
import { createTypeUnsafe, GenericCall, GenericExtrinsic, GenericExtrinsicPayload } from '@polkadot/types';
import { EXTRINSIC_VERSION } from '@polkadot/types/extrinsic/v4/Extrinsic';
import { hexToU8a, isHex, u8aToHex, u8aToU8a } from '@polkadot/util';
import { base64Decode, signatureVerify } from '@polkadot/util-crypto';
import { Args, BaseTxInfo, defineMethod, OptionsWithMeta, UnsignedTransaction } from '@substrate/txwrapper-core';
import { DecodedSignedTx, DecodedSigningPayload, TypeRegistry } from '@substrate/txwrapper-core/lib/types';
import { construct } from '@substrate/txwrapper-polkadot';
import bs58 from 'bs58';
import base32 from 'hi-base32';
import * as _ from 'lodash';
import nacl from 'tweetnacl';
import {
  AddProxyBatchCallArgs,
  BatchArgs,
  BatchCallObject,
  HexString,
  Material,
  ProxyArgs,
  ProxyCallArgs,
  StakeArgs,
  StakeBatchCallArgs,
  StakeMoreArgs,
  StakeMoreCallArgs,
  TransferAllArgs,
  TransferArgs,
  TxMethod,
  UnstakeBatchCallArgs,
} from './iface';
import { KeyPair } from '.';
import { mainnetMetadataRpc, westendMetadataRpc } from '../resources';

const PROXY_METHOD_ARG = 2;
// map to retrieve the address encoding format when the key is the asset name
const coinToAddressMap = new Map<DotAssetTypes, DotAddressFormat>([
  ['dot', DotAddressFormat.polkadot],
  ['tdot', DotAddressFormat.substrate],
]);

export class Utils implements BaseUtils {
  /** @inheritdoc */
  isValidAddress(address: string): boolean {
    try {
      encodeAddress(isHex(address) ? hexToU8a(address) : decodeAddress(address));
      return true;
    } catch (error) {
      return false;
    }
  }

  /** @inheritdoc */
  isValidBlockId(hash: string): boolean {
    return isHex(hash, 256);
  }

  /** @inheritdoc */
  isValidPrivateKey(key: string): boolean {
    try {
      const decodedPrv = hexToU8a(key);
      return decodedPrv.length === nacl.sign.secretKeyLength / 2;
    } catch (e) {
      return false;
    }
  }

  /** @inheritdoc */
  isValidPublicKey(key: string): boolean {
    let pubKey = key;

    // convert base58 pub key to hex format
    // tss common pub is in base58 format and decodes to length of 32
    if (isBase58(pubKey, 32)) {
      const base58Decode = bs58.decode(pubKey);
      pubKey = base58Decode.toString('hex');
    }

    return isValidEd25519PublicKey(pubKey);
  }

  /** @inheritdoc */
  isValidSignature(signature: string): boolean {
    const signatureU8a = u8aToU8a(signature);
    return [64, 65, 66].includes(signatureU8a.length);
  }

  /**
   * Verifies the signature on a given message
   *
   * @param {string} signedMessage the signed message for the signature
   * @param {string} signature the signature to verify
   * @param {string} address the address of the signer
   * @returns {boolean} whether the signature is valid or not
   */
  verifySignature(signedMessage: string, signature: string, address: string): boolean {
    const publicKey = decodeAddress(address);
    const hexPublicKey = u8aToHex(publicKey);

    return signatureVerify(signedMessage, signature, hexPublicKey).isValid;
  }

  /** @inheritdoc */
  isValidTransactionId(txId: string): boolean {
    return isHex(txId, 256);
  }

  /**
   * decodeSeed decodes a dot seed
   *
   * @param {string} seed - the seed to be validated.
   * @returns {Seed} - the object Seed
   */
  decodeSeed(seed: string): Seed {
    const decoded = base32.decode.asBytes(seed);
    return {
      seed: Buffer.from(decoded),
    };
  }

  /**
   * Helper function to capitalize the first letter of a string
   *
   * @param {string} val
   * @returns {string}
   */
  capitalizeFirstLetter(val: string): string {
    return val.charAt(0).toUpperCase() + val.slice(1);
  }

  /**
   * Helper function to decode the internal method hex in case of a proxy transaction
   *
   * @param {string | UnsignedTransaction} tx
   * @param { metadataRpc: string; registry: TypeRegistry } options
   * @returns {TransferArgs}
   */
  decodeCallMethod(
    tx: string | UnsignedTransaction,
    options: { metadataRpc: string; registry: TypeRegistry }
  ): TransferArgs {
    const { registry } = options;
    let methodCall: GenericCall | GenericExtrinsic;
    if (typeof tx === 'string') {
      try {
        const payload: GenericExtrinsicPayload = createTypeUnsafe(registry, 'ExtrinsicPayload', [
          tx,
          { version: EXTRINSIC_VERSION },
        ]);
        methodCall = createTypeUnsafe(registry, 'Call', [payload.method]);
      } catch (e) {
        methodCall = registry.createType('Extrinsic', hexToU8a(tx), {
          isSigned: true,
        });
      }
    } else {
      methodCall = registry.createType('Call', tx.method);
    }
    const method = methodCall.args[PROXY_METHOD_ARG];
    const decodedArgs = method.toJSON() as unknown as ProxyCallArgs;
    return decodedArgs.args;
  }

  /**
   * keyPairFromSeed generates an object with secretKey and publicKey using the polkadot sdk
   * @param seed 32 bytes long seed
   * @returns KeyPair
   */
  keyPairFromSeed(seed: Uint8Array): KeyPair {
    const keyring = new Keyring({ type: 'ed25519' });
    const keyringPair = keyring.addFromSeed(seed);
    const pairJson = keyringPair.toJson();
    const decodedKeyPair = decodePair('', base64Decode(pairJson.encoded), pairJson.encoding.type);
    return new KeyPair({ prv: Buffer.from(decodedKeyPair.secretKey).toString('hex') });
  }

  /**
   * Signing function. Implement this on the OFFLINE signing device.
   *
   * @param {KeyringPair} pair - The signing pair.
   * @param {string} signingPayload - Payload to sign.
   * @param {UnsignedTransaction} transaction - raw transaction to sign
   * @param {Object} options
   * @param {HexString} options.metadataRpc - metadata that is needed for dot to sign
   * @param {TypeRegistry} options.registry - metadata that is needed for dot to sign
   */
  createSignedTx(
    pair: KeyringPair,
    signingPayload: string,
    transaction: UnsignedTransaction,
    options: { metadataRpc: HexString; registry: TypeRegistry }
  ): string {
    const { registry, metadataRpc } = options;
    const { signature } = registry
      .createType('ExtrinsicPayload', signingPayload, {
        version: EXTRINSIC_VERSION,
      })
      .sign(pair);

    // Serialize a signed transaction.
    return this.serializeSignedTransaction(transaction, signature, metadataRpc, registry);
  }

  /**
   * Serializes the signed transaction
   *
   * @param transaction Transaction to serialize
   * @param signature Signature of the message
   * @param metadataRpc Network metadata
   * @param registry Transaction registry
   * @returns string Serialized transaction
   */
  serializeSignedTransaction(transaction, signature, metadataRpc: `0x${string}`, registry): string {
    return construct.signedTx(transaction, signature, {
      metadataRpc,
      registry,
    });
  }

  /**
   * Decodes the dot address from the given format
   *
   * @param {string} address
   * @param {number} [ss58Format]
   * @returns {string}
   */
  decodeDotAddress(address: string, ss58Format: number): string {
    const keypair = new KeyPair({ pub: Buffer.from(decodeAddress(address, undefined, ss58Format)).toString('hex') });
    return keypair.getAddress(ss58Format);
  }

  /**
   * Decodes the dot address from the given format
   *
   * @param {string} address
   * @param {number} [ss58Format]
   * @returns {string}
   */
  encodeDotAddress(address: string, ss58Format?: number): string {
    return encodeAddress(address, ss58Format);
  }

  /**
   * Retrieves the txHash of a signed txHex
   *
   * @param txHex signed transaction hex
   * @returns {string}
   */
  getTxHash(txHex: string): string {
    return construct.txHash(txHex);
  }

  getMaterial(coinConfig: Readonly<CoinConfig>): Material {
    const networkConfig = coinConfig.network as DotNetwork;
    const { specName, specVersion, chainName, txVersion, genesisHash } = networkConfig;
    const metadataRpc = networkConfig.specName === 'westend' ? westendMetadataRpc : mainnetMetadataRpc;

    return {
      specName,
      specVersion,
      chainName,
      metadata: metadataRpc,
      txVersion,
      genesisHash,
    } as Material;
  }

  isSigningPayload(payload: DecodedSigningPayload | DecodedSignedTx): payload is DecodedSigningPayload {
    return (payload as DecodedSigningPayload).blockHash !== undefined;
  }

  isProxyTransfer(arg: TxMethod['args']): arg is ProxyArgs {
    return (arg as ProxyArgs).real !== undefined;
  }

  isTransfer(arg: TxMethod['args']): arg is TransferArgs {
    return (arg as TransferArgs).dest?.id !== undefined && (arg as TransferArgs).value !== undefined;
  }

  isTransferAll(arg: TxMethod['args']): arg is TransferAllArgs {
    return (arg as TransferAllArgs).dest?.id !== undefined && (arg as TransferAllArgs).keepAlive !== undefined;
  }

  /**
   * Returns true if arg is of type BatchArgs, false otherwise.
   *
   * @param arg The object to test.
   *
   * @return true if arg is of type BatchArgs, false otherwise.
   */
  isBatch(arg: TxMethod['args']): arg is BatchArgs {
    return (arg as BatchArgs).calls !== undefined;
  }

  /**
   * Returns true if arg is of type BatchArgs and the calls of the batch are staking calls: a stake
   * call (bond) followed by an add proxy call (addProxy), false otherwise.
   *
   * @param arg The object to test.
   *
   * @return true if arg is of type BatchArgs and the calls of the batch are staking calls: a stake
   * call (bond) followed by an add proxy call (addProxy), false otherwise.
   */
  isStakingBatch(arg: TxMethod['args']): arg is BatchArgs {
    const calls = (arg as BatchArgs).calls;
    if (calls !== undefined) {
      return (
        calls.length === 2 &&
        (this.isStakeBatchCallArgs(calls[0].args) || this.isBondBatchExtra(calls[0].args)) &&
        this.isAddProxyBatchCallArgs(calls[1].args)
      );
    }
    return false;
  }

  /**
   * Returns true if arg is of type StakeBatchCallArgs, false otherwise.
   *
   * @param arg The object to test.
   *
   * @return true if arg is of type StakeBatchCallArgs, false otherwise.
   */
  isStakeBatchCallArgs(arg: BatchCallObject['args']): arg is StakeBatchCallArgs {
    return (arg as StakeBatchCallArgs).value !== undefined && (arg as StakeBatchCallArgs).payee !== undefined;
  }

  /**
   * Returns true if arg is of type AddProxyBatchCallArgs, false otherwise.
   *
   * @param arg The object to test.
   *
   * @return true if arg is of type AddProxyBatchCallArgs, false otherwise.
   */
  isAddProxyBatchCallArgs(arg: BatchCallObject['args']): arg is AddProxyBatchCallArgs {
    return (
      (arg as AddProxyBatchCallArgs).delegate !== undefined &&
      (arg as AddProxyBatchCallArgs).proxy_type !== undefined &&
      (arg as AddProxyBatchCallArgs).delay !== undefined
    );
  }

  /**
   * Returns true if arg is of type BatchArgs and the calls of the batch are unstaking calls: a remove
   * proxy call (removeProxy), followed by a chill call, and an unstake call (unbond), false otherwise.
   *
   * @param arg The object to test.
   *
   * @return true if arg is of type BatchArgs and the calls of the batch are unstaking calls: a remove
   * proxy call (removeProxy), followed by a chill call, and an unstake call (unbond), false otherwise.
   */
  isUnstakingBatch(arg: TxMethod['args']): arg is BatchArgs {
    const calls = (arg as BatchArgs).calls;
    if (calls !== undefined) {
      return (
        calls.length === 3 &&
        this.isRemoveProxyBatchCallArgs(calls[0].args) &&
        _.isEmpty(calls[1].args) &&
        this.isUnstakeBatchCallArgs(calls[2].args)
      );
    }
    return false;
  }

  /**
   * Returns true if arg is of type AddProxyBatchCallArgs, false otherwise.
   *
   * @param arg The object to test.
   *
   * @return true if arg is of type AddProxyBatchCallArgs, false otherwise.
   */
  isRemoveProxyBatchCallArgs(arg: BatchCallObject['args']): arg is AddProxyBatchCallArgs {
    return (
      (arg as AddProxyBatchCallArgs).delegate !== undefined &&
      (arg as AddProxyBatchCallArgs).proxy_type !== undefined &&
      (arg as AddProxyBatchCallArgs).delay !== undefined
    );
  }

  /**
   * Returns true if arg is of type UnstakeBatchCallArgs, false otherwise.
   *
   * @param arg The object to test.
   *
   * @return true if arg is of type UnstakeBatchCallArgs, false otherwise.
   */
  isUnstakeBatchCallArgs(arg: BatchCallObject['args']): arg is UnstakeBatchCallArgs {
    return (arg as UnstakeBatchCallArgs).value !== undefined;
  }

  /**
   * Returns true if arg is of type StakeArgs, false otherwise.
   *
   * @param arg The object to test.
   *
   * @return true if arg is of type StakeArgs, false otherwise.
   */
  isBond(arg: TxMethod['args']): arg is StakeArgs {
    return (arg as StakeArgs).value !== undefined && (arg as StakeArgs).payee !== undefined;
  }

  /**
   * Returns true if arg is of type StakeMoreArgs, false otherwise.
   *
   * @param arg The object to test.
   *
   * @return true if arg is of type StakeMoreArgs, false otherwise.
   */
  isBondExtra(arg: TxMethod['args'] | BatchCallObject['args']): arg is StakeMoreArgs {
    return (arg as StakeMoreArgs).maxAdditional !== undefined;
  }

  /**
   * Returns true if arg is of type StakeMoreArgs, false otherwise.
   *
   * @param arg The object to test.
   *
   * @return true if arg is of type StakeMoreArgs, false otherwise.
   */
  isBondBatchExtra(arg: BatchCallObject['args']): arg is StakeMoreCallArgs {
    return (arg as StakeMoreCallArgs).max_additional !== undefined;
  }

  /**
   * extracts and returns the signature in hex format given a raw signed transaction
   *
   * @param {string} rawTx signed raw transaction
   * @param options registry dot registry used to retrieve the signature
   */
  recoverSignatureFromRawTx(rawTx: string, options: { registry: TypeRegistry }): string {
    const { registry } = options;
    const methodCall = registry.createType('Extrinsic', rawTx, {
      isSigned: true,
    });
    let signature = u8aToHex(methodCall.signature) as string;

    // remove 0x from the signature since this is how it's returned from TSS signing
    if (signature.startsWith('0x')) {
      signature = signature.substr(2);
    }
    return signature;
  }

  /**
   * Decodes the dot address from the given format
   *
   * @param {string} address
   * @param {number} [ss58Format]
   * @returns {KeyPair}
   */
  decodeDotAddressToKeyPair(address: string, ss58Format?: number): KeyPair {
    return new KeyPair({ pub: Buffer.from(decodeAddress(address, undefined, ss58Format)).toString('hex') });
  }

  /**
   * Checks whether the given input is a hex string with with 0 value
   * used to check whether a given transaction is immortal or mortal
   * @param hexValue
   */
  isZeroHex(hexValue: string): boolean {
    return hexValue === '0x00';
  }

  /**
   * Takes an asset name and returns the respective address to format to
   * since polkadot addresses differ depending on the network
   * ref: https://wiki.polkadot.network/docs/learn-accounts
   * @param networkCoinName
   */
  getAddressFormat(networkCoinName: DotAssetTypes): DotAddressFormat {
    return coinToAddressMap.get(networkCoinName) as DotAddressFormat;
  }

  /**
   * Creates a pure proxy extrinsic. Polkadot has renamed anonymous proxies to pure proxies, but
   * the libraries we are using to build transactions have not been updated, as a stop gap we are
   * defining the pure proxy extrinsic here.
   *
   * @param args Arguments to the createPure extrinsic.
   * @param info Common information to all transactions.
   * @param options Chain registry and metadata.
   */
  pureProxy(args: PureProxyArgs, info: BaseTxInfo, options: OptionsWithMeta): UnsignedTransaction {
    return defineMethod(
      {
        method: {
          args,
          name: 'createPure',
          pallet: 'proxy',
        },
        ...info,
      },
      options
    );
  }

  /**
   * Removes '0x' from a given `string` if present.
   *
   * @param {string} str the string value.
   *
   * @return {string} a string without a '0x' prefix.
   */
  stripHexPrefix(str: string): string {
    return this.isHexPrefixed(str) ? str.slice(2) : str;
  }

  /**
   * Returns true if a string starts with '0x', false otherwise.
   *
   * @param {string} str the string value.
   *
   * @return {boolean} true if a string starts with '0x', false otherwise.
   */
  isHexPrefixed(str: string): boolean {
    return str.slice(0, 2) === '0x';
  }
}

interface PureProxyArgs extends Args {
  proxyType: string;
  delay: number;
  index: number;
}

const utils = new Utils();

export default utils;

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


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