PHP WebShell

Текущая директория: /opt/BitGoJS/modules/abstract-utxo/src/transaction/fixedScript

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

import * as utxolib from '@bitgo/utxo-lib';
import { bip32, BIP32Interface, bitgo } from '@bitgo/utxo-lib';
import { Triple } from '@bitgo/sdk-core';

import { Output, TransactionExplanation, FixedScriptWalletOutput } from '../../abstractUtxoCoin';
import { toExtendedAddressFormat } from '../recipient';

export type ChangeAddressInfo = { address: string; chain: number; index: number };

function explainCommon<TNumber extends number | bigint>(
  tx: bitgo.UtxoTransaction<TNumber>,
  params: {
    changeInfo?: ChangeAddressInfo[];
    feeInfo?: string;
  },
  network: utxolib.Network
) {
  const displayOrder = ['id', 'outputAmount', 'changeAmount', 'outputs', 'changeOutputs'];
  let spendAmount = BigInt(0);
  let changeAmount = BigInt(0);
  const changeOutputs: FixedScriptWalletOutput[] = [];
  const outputs: Output[] = [];

  const { changeInfo } = params;
  const changeAddresses = changeInfo?.map((info) => info.address) ?? [];

  tx.outs.forEach((currentOutput) => {
    // Try to encode the script pubkey with an address. If it fails, try to parse it as an OP_RETURN output with the prefix.
    // If that fails, then it is an unrecognized scriptPubkey and should fail
    const currentAddress = toExtendedAddressFormat(currentOutput.script, network);
    const currentAmount = BigInt(currentOutput.value);

    if (changeAddresses.includes(currentAddress)) {
      // this is change
      changeAmount += currentAmount;
      const change = changeInfo?.find((change) => change.address === currentAddress);

      if (!change) {
        throw new Error('changeInfo must have change information for all change outputs');
      }
      changeOutputs.push({
        address: currentAddress,
        amount: currentAmount.toString(),
        chain: change.chain,
        index: change.index,
        external: false,
      });
      return;
    }

    spendAmount += currentAmount;
    outputs.push({
      address: currentAddress,
      amount: currentAmount.toString(),
      // If changeInfo has a length greater than or equal to zero, it means that the change information
      // was provided to the function but the output was not identified as change. In this case,
      // the output is external, and we can set it as so. If changeInfo is undefined, it means we were
      // given no information about change outputs, so we can't determine anything about the output,
      // so we leave it undefined.
      external: changeInfo ? true : undefined,
    });
  });

  const outputDetails = {
    outputAmount: spendAmount.toString(),
    changeAmount: changeAmount.toString(),
    outputs,
    changeOutputs,
  };

  let fee: string | undefined;
  let locktime: number | undefined;

  if (params.feeInfo) {
    displayOrder.push('fee');
    fee = params.feeInfo;
  }

  if (Number.isInteger(tx.locktime) && tx.locktime > 0) {
    displayOrder.push('locktime');
    locktime = tx.locktime;
  }

  return { displayOrder, id: tx.getId(), ...outputDetails, fee, locktime };
}

function getRootWalletKeys(params: { pubs?: string[] }) {
  const keys = params.pubs?.map((xpub) => bip32.fromBase58(xpub));
  return keys && keys.length === 3 ? new bitgo.RootWalletKeys(keys as Triple<BIP32Interface>) : undefined;
}

function getPsbtInputSignaturesCount(
  psbt: bitgo.UtxoPsbt,
  params: {
    pubs?: string[];
  }
) {
  const rootWalletKeys = getRootWalletKeys(params);
  return rootWalletKeys
    ? bitgo.getSignatureValidationArrayPsbt(psbt, rootWalletKeys).map((sv) => sv[1].filter((v) => v).length)
    : (Array(psbt.data.inputs.length) as number[]).fill(0);
}

function getTxInputSignaturesCount<TNumber extends number | bigint>(
  tx: bitgo.UtxoTransaction<TNumber>,
  params: {
    txInfo?: { unspents?: bitgo.Unspent<TNumber>[] };
    pubs?: string[];
  },
  network: utxolib.Network
) {
  const prevOutputs = params.txInfo?.unspents?.map((u) => bitgo.toOutput<TNumber>(u, network));
  const rootWalletKeys = getRootWalletKeys(params);
  const { unspents = [] } = params.txInfo ?? {};

  // get the number of signatures per input
  return tx.ins.map((input, idx): number => {
    if (unspents.length !== tx.ins.length) {
      return 0;
    }
    if (!prevOutputs) {
      throw new Error(`invalid state`);
    }
    if (!rootWalletKeys) {
      // no pub keys or incorrect number of pub keys
      return 0;
    }
    try {
      return bitgo.verifySignatureWithUnspent<TNumber>(tx, idx, unspents, rootWalletKeys).filter((v) => v).length;
    } catch (e) {
      // some other error occurred and we can't validate the signatures
      return 0;
    }
  });
}

/**
 * Decompose a raw psbt into useful information, such as the total amounts,
 * change amounts, and transaction outputs.
 */
export function explainPsbt<TNumber extends number | bigint, Tx extends bitgo.UtxoTransaction<bigint>>(
  psbt: bitgo.UtxoPsbt<Tx>,
  params: {
    pubs?: string[];
    txInfo?: { unspents?: bitgo.Unspent<TNumber>[] };
  },
  network: utxolib.Network
): TransactionExplanation {
  const txOutputs = psbt.txOutputs;

  function getChainAndIndexFromBip32Derivations(output: bitgo.PsbtOutput) {
    const derivations = output.bip32Derivation ?? output.tapBip32Derivation ?? undefined;
    if (!derivations) {
      return undefined;
    }
    const paths = derivations.map((d) => d.path);
    if (!paths || paths.length !== 3) {
      throw new Error('expected 3 paths in bip32Derivation or tapBip32Derivation');
    }
    if (!paths.every((p) => paths[0] === p)) {
      throw new Error('expected all paths to be the same');
    }

    paths.forEach((path) => {
      if (paths[0] !== path) {
        throw new Error(
          'Unable to get a single chain and index on the output because there are different paths for different keys'
        );
      }
    });
    return utxolib.bitgo.getChainAndIndexFromPath(paths[0]);
  }

  function getChangeInfo() {
    try {
      return utxolib.bitgo.findInternalOutputIndices(psbt).map((i) => {
        const derivationInformation = getChainAndIndexFromBip32Derivations(psbt.data.outputs[i]);
        if (!derivationInformation) {
          throw new Error('could not find derivation information on bip32Derivation or tapBip32Derivation');
        }
        return {
          address: utxolib.address.fromOutputScript(txOutputs[i].script, network),
          external: false,
          ...derivationInformation,
        };
      });
    } catch (e) {
      if (e instanceof utxolib.bitgo.ErrorNoMultiSigInputFound) {
        return undefined;
      }
      throw e;
    }
  }
  const changeInfo = getChangeInfo();
  const tx = psbt.getUnsignedTx() as bitgo.UtxoTransaction<TNumber>;
  const common = explainCommon(tx, { ...params, changeInfo }, network);
  const inputSignaturesCount = getPsbtInputSignaturesCount(psbt, params);

  // Set fee from subtracting inputs from outputs
  const outputAmount = txOutputs.reduce((cumulative, curr) => cumulative + BigInt(curr.value), BigInt(0));
  const inputAmount = psbt.txInputs.reduce((cumulative, txInput, i) => {
    const data = psbt.data.inputs[i];
    if (data.witnessUtxo) {
      return cumulative + BigInt(data.witnessUtxo.value);
    } else if (data.nonWitnessUtxo) {
      const tx = bitgo.createTransactionFromBuffer<bigint>(data.nonWitnessUtxo, network, { amountType: 'bigint' });
      return cumulative + BigInt(tx.outs[txInput.index].value);
    } else {
      throw new Error('could not find value on input');
    }
  }, BigInt(0));

  return {
    ...common,
    fee: (inputAmount - outputAmount).toString(),
    inputSignatures: inputSignaturesCount,
    signatures: inputSignaturesCount.reduce((prev, curr) => (curr > prev ? curr : prev), 0),
  } as TransactionExplanation;
}

export function explainLegacyTx<TNumber extends number | bigint>(
  tx: bitgo.UtxoTransaction<TNumber>,
  params: {
    pubs?: string[];
    txInfo?: { unspents?: bitgo.Unspent<TNumber>[] };
    changeInfo?: { address: string; chain: number; index: number }[];
  },
  network: utxolib.Network
): TransactionExplanation {
  const common = explainCommon(tx, params, network);
  const inputSignaturesCount = getTxInputSignaturesCount(tx, params, network);
  return {
    ...common,
    inputSignatures: inputSignaturesCount,
    signatures: inputSignaturesCount.reduce((prev, curr) => (curr > prev ? curr : prev), 0),
  } as TransactionExplanation;
}

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


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