PHP WebShell

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

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

import { BIP32Interface } from 'bip32';
import { Transaction, TxOutput } from 'bitcoinjs-lib';
import * as utxolib from '../../../src';
import {
  createOutputScript2of3,
  isScriptType2Of3,
  isSupportedScriptType,
  ScriptType2Of3,
  scriptTypes2Of3,
} from '../../../src/bitgo/outputScripts';
import { getDefaultCosigner, KeyTriple } from '../../../src/testutil';

import {
  isTriple,
  createTransactionBuilderForNetwork,
  createTransactionFromBuffer,
  signInput2Of3,
  TxOutPoint,
  UtxoTransaction,
  createPsbtForNetwork,
  ChainCode,
  RootWalletKeys,
  addWalletUnspentToPsbt,
  fromOutputWithPrevTx,
  WalletUnspent,
  KeyName,
  addWalletOutputToPsbt,
  Tuple,
} from '../../../src/bitgo';
import { scriptTypeForChain } from '../../../src/bitgo';

export const scriptTypesSingleSig = ['p2pkh', 'p2wkh'] as const;
export type ScriptTypeSingleSig = (typeof scriptTypesSingleSig)[number];

export const scriptTypes = [...scriptTypesSingleSig, ...scriptTypes2Of3];
export type ScriptType = ScriptType2Of3 | ScriptTypeSingleSig;

type Network = utxolib.Network;

export function isSupportedDepositType(network: Network, scriptType: ScriptType): boolean {
  if (scriptType === 'p2pkh') {
    return true;
  }

  if (scriptType === 'p2wkh') {
    return utxolib.supportsSegwit(network);
  }

  return isSupportedScriptType(network, scriptType);
}

export function isSupportedSpendType(network: Network, scriptType: ScriptType): boolean {
  return isScriptType2Of3(scriptType) && isSupportedScriptType(network, scriptType);
}

/**
 *
 * @param keys - Pubkeys to use for generating the address.
 *               If scriptType is single-sig, the first key will be used.
 * @param scriptType
 * @param network
 * @return {Buffer} scriptPubKey
 */
export function createScriptPubKey(keys: KeyTriple, scriptType: ScriptType, network: Network): Buffer {
  const pubkeys = keys.map((k) => k.publicKey);

  switch (scriptType) {
    case 'p2sh':
    case 'p2shP2wsh':
    case 'p2wsh':
    case 'p2tr':
    case 'p2trMusig2':
      return createOutputScript2of3(pubkeys, scriptType).scriptPubKey;
    case 'p2pkh':
      return utxolib.payments.p2pkh({ pubkey: keys[0].publicKey }).output as Buffer;
    case 'p2wkh':
      return utxolib.payments.p2wpkh({ pubkey: keys[0].publicKey }).output as Buffer;
    default:
      throw new Error(`unsupported output type ${scriptType}`);
  }
}

export function createSpendTransactionFromPrevOutputs<TNumber extends number | bigint>(
  keys: KeyTriple,
  scriptType: ScriptType2Of3,
  prevOutputs: (TxOutPoint & TxOutput<TNumber>)[],
  recipientScript: Buffer,
  network: Network,
  {
    signKeys = [keys[0], keys[2]],
    version,
    amountType,
  }: { signKeys?: BIP32Interface[]; version?: number; amountType?: 'number' | 'bigint' } = {}
): UtxoTransaction<TNumber> {
  if (signKeys.length !== 1 && signKeys.length !== 2) {
    throw new Error(`signKeys length must be 1 or 2`);
  }

  const txBuilder = createTransactionBuilderForNetwork<TNumber>(network, { version });

  prevOutputs.forEach(({ txid, vout, script, value }, i) => {
    txBuilder.addInput(txid, vout, undefined, script, value);
  });

  const inputSum = prevOutputs.reduce((sum, { value }) => sum + BigInt(value), BigInt(0));
  const fee = network === utxolib.networks.dogecoinTest ? BigInt(1_000_000) : BigInt(1_000);
  const outputValue = inputSum - fee;

  txBuilder.addOutput(recipientScript, (amountType === 'number' ? Number(outputValue) : outputValue) as TNumber);

  const publicKeys = keys.map((k) => k.publicKey);
  if (!isTriple(publicKeys)) {
    throw new Error();
  }

  prevOutputs.forEach(({ value }, vin) => {
    signKeys.forEach((key) => {
      signInput2Of3(txBuilder, vin, scriptType, publicKeys, key, getDefaultCosigner(publicKeys, key.publicKey), value);
    });
  });

  if (signKeys.length === 1) {
    return txBuilder.buildIncomplete() as UtxoTransaction<TNumber>;
  }
  return txBuilder.build() as UtxoTransaction<TNumber>;
}

export function createSpendTransaction<TNumber extends number | bigint = number>(
  keys: KeyTriple,
  scriptType: ScriptType2Of3,
  inputTxs: Buffer[],
  recipientScript: Buffer,
  network: Network,
  version?: number,
  amountType?: 'number' | 'bigint'
): Transaction<TNumber> {
  const matches: (TxOutPoint & TxOutput<TNumber>)[] = inputTxs
    .map((inputTxBuffer): (TxOutPoint & TxOutput<TNumber>)[] => {
      const inputTx = createTransactionFromBuffer<TNumber>(inputTxBuffer, network, { amountType });

      const { scriptPubKey } = createOutputScript2of3(
        keys.map((k) => k.publicKey),
        scriptType as ScriptType2Of3
      );

      return inputTx.outs
        .map((o, vout): (TxOutPoint & TxOutput<TNumber>) | undefined => {
          if (!scriptPubKey.equals(o.script)) {
            return;
          }
          return {
            txid: inputTx.getId(),
            vout,
            value: o.value,
            script: o.script,
          };
        })
        .filter((v): v is TxOutPoint & TxOutput<TNumber> => v !== undefined);
    })
    .reduce((all, matches) => [...all, ...matches]);

  if (!matches.length) {
    throw new Error(`could not find matching outputs in funding transaction`);
  }

  return createSpendTransactionFromPrevOutputs<TNumber>(keys, scriptType, matches, recipientScript, network, {
    version,
    amountType,
  });
}

export function createPsbtSpendTransactionFromPrevTx(
  rootWalletKeys: RootWalletKeys,
  unspents: WalletUnspent<bigint>[],
  network: Network,
  signers: Tuple<KeyName> = ['user', 'bitgo'],
  version?: number
): UtxoTransaction<bigint> {
  const psbt = createPsbtForNetwork({ network }, { version });

  unspents.forEach((u, index) => {
    addWalletUnspentToPsbt(psbt, u, rootWalletKeys, signers[0], signers[1]);
  });

  const inputSum = unspents.reduce((sum, { value }) => sum + BigInt(value), BigInt(0));
  const fee = network === utxolib.networks.dogecoinTest ? BigInt(1_000_000) : BigInt(1_000);
  const outputValue = inputSum - fee;

  addWalletOutputToPsbt(psbt, rootWalletKeys, unspents[0].chain, unspents[0].index, outputValue);

  signers.forEach((keyName) => {
    psbt.setAllInputsMusig2NonceHD(rootWalletKeys[keyName]);
  });

  signers.forEach((keyName) => {
    psbt.signAllInputsHD(rootWalletKeys[keyName]);
  });

  if (!psbt.validateSignaturesOfAllInputs()) {
    throw new Error('psbt sig validation fails');
  }

  psbt.finalizeAllInputs();
  return psbt.extractTransaction();
}

export function createPsbtSpendTransaction<TNumber extends number | bigint = number>({
  rootWalletKeys,
  signers,
  chain,
  index,
  inputTxs,
  network,
  version,
  amountType,
}: {
  rootWalletKeys: RootWalletKeys;
  signers: Tuple<KeyName>;
  chain: ChainCode;
  index: number;
  inputTxs: Buffer[];
  network: Network;
  version?: number;
  amountType?: 'number' | 'bigint';
}): Transaction<TNumber> {
  const walletKeys = rootWalletKeys.deriveForChainAndIndex(chain, index);
  const { scriptPubKey } = createOutputScript2of3(walletKeys.publicKeys, scriptTypeForChain(chain));

  const matches = inputTxs
    .map((inputTxBuffer): WalletUnspent<bigint>[] => {
      const inputTx = createTransactionFromBuffer<bigint>(inputTxBuffer, network, { amountType: 'bigint' });

      return inputTx.outs
        .map((o, vout): WalletUnspent<bigint> | undefined => {
          if (!scriptPubKey.equals(o.script)) {
            return;
          }
          return { chain, index, ...fromOutputWithPrevTx<bigint>(inputTx, vout) };
        })
        .filter((v): v is WalletUnspent<bigint> => v !== undefined);
    })
    .reduce((all, matches) => [...all, ...matches]);

  if (!matches.length) {
    throw new Error(`could not find matching outputs in funding transaction`);
  }

  const tx = createPsbtSpendTransactionFromPrevTx(rootWalletKeys, matches, network, signers, version);
  return tx.clone(amountType) as Transaction<TNumber>;
}

/**
 * @returns BIP32 hardcoded index for p2trMusig2 spend type. 0 for key path and 100 for script path.
 * For same fixture key triple and script type (p2trMusig2),
 * we need 2 different deposit and spend tx fixtures.
 */
export function getP2trMusig2Index(spendType: 'keyPath' | 'scriptPath'): number {
  return spendType === 'keyPath' ? 0 : 100;
}

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


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