PHP WebShell

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

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

import * as assert from 'assert';

import { ScriptType, ScriptType2Of3, scriptTypeP2shP2pk, scriptTypes2Of3 } from '../bitgo/outputScripts';
import {
  getExternalChainCode,
  isWalletUnspent,
  KeyName,
  getInternalChainCode,
  RootWalletKeys,
  Unspent,
  UtxoTransactionBuilder,
  createTransactionBuilderForNetwork,
  addToTransactionBuilder,
  getWalletAddress,
  signInputP2shP2pk,
  signInputWithUnspent,
  WalletUnspentSigner,
} from '../bitgo';
import { Network } from '../networks';
import { mockReplayProtectionUnspent, mockWalletUnspent } from './mock';

/**
 * input script type and value.
 */
export type TxnInputScriptType = Exclude<ScriptType, 'p2trMusig2'>;
export type TxnOutputScriptType = ScriptType2Of3;

/**
 * output script type and value
 */
export interface TxnInput<TNumber extends number | bigint> {
  scriptType: TxnInputScriptType;
  value: TNumber;
}

/**
 * should set either address or scriptType, never both.
 * set isInternalAddress=true for internal output address
 */
export interface TxnOutput<TNumber extends number | bigint> {
  address?: string;
  scriptType?: TxnOutputScriptType;
  value: TNumber;
  isInternalAddress?: boolean;
}

/**
 * array of supported input script types.
 */
export const txnInputScriptTypes = ['p2sh', 'p2shP2wsh', 'p2wsh', 'p2tr', scriptTypeP2shP2pk] as const;

/**
 * array of supported output script types.
 */
export const txnOutputScriptTypes = scriptTypes2Of3;

/**
 * create unspent object from input script type, index, network and root wallet key.
 */
export function toTxnUnspent<TNumber extends number | bigint>(
  input: TxnInput<TNumber>,
  index: number,
  network: Network,
  rootWalletKeys: RootWalletKeys
): Unspent<TNumber> {
  if (input.scriptType === 'p2shP2pk') {
    return mockReplayProtectionUnspent<TNumber>(network, input.value, { key: rootWalletKeys['user'], vout: index });
  } else {
    return mockWalletUnspent<TNumber>(network, input.value, {
      chain: getInternalChainCode(input.scriptType),
      vout: index,
      keys: rootWalletKeys,
      index,
    });
  }
}

/**
 * returns signer and cosigner names for TxnInputScriptType.
 * user and undefined as signer and cosigner respectively for p2shP2pk.
 * user and bitgo as signer and cosigner respectively for other input script types.
 */
export function getTxnSigners(inputType: TxnInputScriptType): { signerName: KeyName; cosignerName?: KeyName } {
  return {
    signerName: 'user',
    cosignerName: inputType === 'p2shP2pk' ? undefined : 'bitgo',
  };
}

/**
 * signs with first or second signature for single input.
 * p2shP2pk is signed only with first sign.
 */
export function signTxnInput<TNumber extends number | bigint>(
  txb: UtxoTransactionBuilder<TNumber>,
  input: TxnInput<TNumber>,
  inputIndex: number,
  rootWalletKeys: RootWalletKeys,
  sign: 'halfsigned' | 'fullsigned',
  signers?: { signerName: KeyName; cosignerName?: KeyName }
): void {
  const { signerName, cosignerName } = signers ? signers : getTxnSigners(input.scriptType);
  const unspent = toTxnUnspent(input, inputIndex, txb.network, rootWalletKeys);
  if (sign === 'halfsigned') {
    if (input.scriptType === 'p2shP2pk') {
      signInputP2shP2pk(txb, inputIndex, rootWalletKeys[signerName]);
    } else if (isWalletUnspent(unspent) && cosignerName) {
      signInputWithUnspent(
        txb,
        inputIndex,
        unspent,
        WalletUnspentSigner.from(rootWalletKeys, rootWalletKeys[signerName], rootWalletKeys[cosignerName])
      );
    }
  }
  if (isWalletUnspent(unspent) && sign === 'fullsigned' && cosignerName) {
    signInputWithUnspent(
      txb,
      inputIndex,
      unspent,
      WalletUnspentSigner.from(rootWalletKeys, rootWalletKeys[cosignerName], rootWalletKeys[signerName])
    );
  }
}

/**
 * signs with first or second signature for all inputs.
 * p2shP2pk is signed only with first sign.
 */
export function signAllTxnInputs<TNumber extends number | bigint>(
  txb: UtxoTransactionBuilder<TNumber>,
  inputs: TxnInput<TNumber>[],
  rootWalletKeys: RootWalletKeys,
  sign: 'halfsigned' | 'fullsigned',
  signers?: { signerName: KeyName; cosignerName?: KeyName }
): void {
  inputs.forEach((input, index) => {
    signTxnInput(txb, input, index, rootWalletKeys, sign, signers);
  });
}

/**
 * construct transaction for given inputs, outputs, network and root wallet keys.
 */
export function constructTxnBuilder<TNumber extends number | bigint>(
  inputs: TxnInput<TNumber>[],
  outputs: TxnOutput<TNumber>[],
  network: Network,
  rootWalletKeys: RootWalletKeys,
  sign: 'unsigned' | 'halfsigned' | 'fullsigned',
  signers?: { signerName: KeyName; cosignerName?: KeyName }
): UtxoTransactionBuilder<TNumber> {
  const totalInputAmount = inputs.reduce((sum, input) => sum + BigInt(input.value), BigInt(0));
  const outputInputAmount = outputs.reduce((sum, output) => sum + BigInt(output.value), BigInt(0));
  assert(totalInputAmount >= outputInputAmount, 'total output can not exceed total input');
  assert(
    !outputs.some((o) => (o.scriptType && o.address) || (!o.scriptType && !o.address)),
    'only either output script type or address should be provided'
  );

  const txb = createTransactionBuilderForNetwork<TNumber>(network);

  const unspents = inputs.map((input, i) => toTxnUnspent(input, i, network, rootWalletKeys));

  unspents.forEach((u, i) => {
    addToTransactionBuilder(txb, u);
  });

  outputs.forEach((output, i) => {
    const address = output.scriptType
      ? getWalletAddress(
          rootWalletKeys,
          output.isInternalAddress ? getInternalChainCode(output.scriptType) : getExternalChainCode(output.scriptType),
          i,
          network
        )
      : output.address;
    if (!address) {
      throw new Error('address is missing');
    }
    txb.addOutput(address, output.value);
  });

  if (sign === 'unsigned') {
    return txb;
  }

  signAllTxnInputs(txb, inputs, rootWalletKeys, 'halfsigned', signers);

  if (sign === 'fullsigned') {
    signAllTxnInputs(txb, inputs, rootWalletKeys, sign, signers);
  }

  return txb;
}

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


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