PHP WebShell

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

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

import {
  addToTransactionBuilder,
  getLeafVersion,
  calculateScriptPathLevel,
  createTransactionBuilderForNetwork,
  getInternalChainCode,
  getWalletAddress,
  isPlaceholderSignature,
  isValidControlBock,
  isWalletUnspent,
  outputScripts,
  ParsedSignatureScriptP2ms,
  ParsedSignatureScriptTaproot,
  ParsedSignatureScriptTaprootScriptPath,
  parseSignatureScript2Of3,
  RootWalletKeys,
  scriptTypeForChain,
  signInputWithUnspent,
  toTNumber,
  Unspent,
  unspentSum,
  UtxoPsbt,
  UtxoTransaction,
  UtxoTransactionBuilder,
  WalletUnspent,
  WalletUnspentSigner,
  KeyName,
  ParsedPsbtP2ms,
  ParsedPsbtTaproot,
} from '../../../src/bitgo';
import { parsePsbtInput, signWalletPsbt } from '../../../src/bitgo/wallet/Psbt';
import * as assert from 'assert';
import { SignatureTargetType } from './Psbt';
import { Network } from '../../../src';

function validateScript(
  psbtParsed: ParsedPsbtP2ms | ParsedPsbtTaproot,
  txParsed: ParsedSignatureScriptP2ms | ParsedSignatureScriptTaproot | undefined
) {
  if (txParsed === undefined) {
    assert.deepStrictEqual(Buffer.isBuffer(psbtParsed.pubScript), true);

    if (psbtParsed.scriptType === 'p2sh') {
      assert.deepStrictEqual(Buffer.isBuffer(psbtParsed.redeemScript), true);
      assert.deepStrictEqual(Buffer.isBuffer(psbtParsed.witnessScript), false);
    } else if (psbtParsed.scriptType === 'p2wsh') {
      assert.deepStrictEqual(Buffer.isBuffer(psbtParsed.redeemScript), false);
      assert.deepStrictEqual(Buffer.isBuffer(psbtParsed.witnessScript), true);
    } else if (psbtParsed.scriptType === 'p2shP2wsh') {
      assert.deepStrictEqual(Buffer.isBuffer(psbtParsed.redeemScript), true);
      assert.deepStrictEqual(Buffer.isBuffer(psbtParsed.witnessScript), true);
    } else if (psbtParsed.scriptType === 'taprootScriptPathSpend') {
      assert.deepStrictEqual(isValidControlBock(psbtParsed.controlBlock), true);
      assert.deepStrictEqual(psbtParsed.scriptPathLevel, calculateScriptPathLevel(psbtParsed.controlBlock));
      assert.deepStrictEqual(psbtParsed.leafVersion, getLeafVersion(psbtParsed.controlBlock));
    }
  } else {
    assert.ok(txParsed.scriptType !== 'taprootKeyPathSpend');
    assert.deepStrictEqual(txParsed.scriptType, psbtParsed.scriptType);
    assert.deepStrictEqual(txParsed.pubScript, psbtParsed.pubScript);

    if (
      (txParsed.scriptType === 'p2sh' && psbtParsed.scriptType === 'p2sh') ||
      (txParsed.scriptType === 'p2wsh' && psbtParsed.scriptType === 'p2wsh') ||
      (txParsed.scriptType === 'p2shP2wsh' && psbtParsed.scriptType === 'p2shP2wsh')
    ) {
      assert.deepStrictEqual(txParsed.redeemScript, psbtParsed.redeemScript);
      assert.deepStrictEqual(txParsed.witnessScript, psbtParsed.witnessScript);
    } else if (txParsed.scriptType === 'taprootScriptPathSpend' && psbtParsed.scriptType === 'taprootScriptPathSpend') {
      // To ensure script path p2tr
      assert.deepStrictEqual(txParsed.publicKeys, psbtParsed.publicKeys);
      const txParsedP2trScriptPath = txParsed as ParsedSignatureScriptTaprootScriptPath;
      assert.deepStrictEqual(txParsedP2trScriptPath.controlBlock, psbtParsed.controlBlock);
      assert.deepStrictEqual(txParsedP2trScriptPath.scriptPathLevel, psbtParsed.scriptPathLevel);
      assert.deepStrictEqual(txParsedP2trScriptPath.leafVersion, psbtParsed.leafVersion);
    }
  }
}

function validatePublicKeys(
  psbtParsed: ParsedPsbtP2ms | ParsedPsbtTaproot,
  txParsed: ParsedSignatureScriptP2ms | ParsedSignatureScriptTaproot | undefined
) {
  if (txParsed === undefined) {
    assert.deepStrictEqual(psbtParsed.publicKeys.length, 3);
    psbtParsed.publicKeys.forEach((publicKey) => {
      assert.deepStrictEqual(Buffer.isBuffer(publicKey), true);
    });
  } else {
    assert.ok(txParsed.scriptType !== 'taprootKeyPathSpend');
    assert.deepStrictEqual(txParsed.publicKeys.length, psbtParsed.publicKeys?.length);
    const pubKeyMatch = txParsed.publicKeys.every((txPubKey) =>
      psbtParsed.publicKeys?.some((psbtPubKey) => psbtPubKey.equals(txPubKey))
    );
    assert.deepStrictEqual(pubKeyMatch, true);
  }
}

function validateSignature(
  psbtParsed: ParsedPsbtP2ms | ParsedPsbtTaproot,
  txParsed: ParsedSignatureScriptP2ms | ParsedSignatureScriptTaproot | undefined
) {
  if (txParsed === undefined) {
    assert.deepStrictEqual(psbtParsed.signatures, undefined);
  } else {
    const txSignatures = txParsed.signatures.filter(
      (txSig) => Buffer.isBuffer(txSig) && !isPlaceholderSignature(txSig)
    );
    assert.deepStrictEqual(txSignatures.length, psbtParsed.signatures?.length);
    if (txSignatures.length < 1) {
      return;
    }
    const sigMatch = txSignatures.every((txSig) =>
      Buffer.isBuffer(txSig) ? psbtParsed.signatures?.some((psbtSig) => psbtSig.equals(txSig)) : true
    );
    assert.deepStrictEqual(sigMatch, true);
  }
}

export function validatePsbtParsing(
  tx: UtxoTransaction<bigint>,
  psbt: UtxoPsbt,
  unspents: WalletUnspent<bigint>[],
  signatureTarget: SignatureTargetType
): void {
  unspents.forEach((u, i) => {
    if (!isWalletUnspent(u)) {
      return;
    }
    const scriptType = scriptTypeForChain(u.chain);

    if (signatureTarget === 'unsigned') {
      if (scriptType === 'p2tr') {
        assert.throws(
          () => parsePsbtInput(psbt.data.inputs[i]),
          (e: any) => e.message === 'could not parse input'
        );
      } else {
        const psbtParsed = parsePsbtInput(psbt.data.inputs[i]);
        assert.deepStrictEqual(psbtParsed.scriptType, scriptType);
        validateScript(psbtParsed, undefined);
        validatePublicKeys(psbtParsed, undefined);
        validateSignature(psbtParsed, undefined);
      }
    } else {
      const psbtParsed = parsePsbtInput(psbt.data.inputs[i]);
      assert.strictEqual(psbtParsed.scriptType, scriptType === 'p2tr' ? 'taprootScriptPathSpend' : scriptType);
      const txParsed = parseSignatureScript2Of3(tx.ins[i]);
      validateScript(psbtParsed, txParsed);
      validatePublicKeys(psbtParsed, txParsed);
      validateSignature(psbtParsed, txParsed);
    }
  });
}

export function assertEqualTransactions<TNumber extends number | bigint>(
  txOne: UtxoTransaction<TNumber>,
  txTwo: UtxoTransaction<TNumber>
): void {
  assert.ok(txOne.network === txTwo.network);
  assert.ok(txOne.getId() === txTwo.getId());
  assert.ok(txOne.toHex() === txTwo.toHex());
  assert.ok(txOne.virtualSize() === txTwo.virtualSize());
  assert.ok(txOne.locktime === txTwo.locktime);
  assert.ok(txOne.version === txTwo.version);
  assert.ok(txOne.weight() === txTwo.weight());

  assert.ok(txOne.ins.length === txTwo.ins.length);
  assert.ok(txOne.outs.length === txTwo.outs.length);
  txOne.ins.forEach((_, i) => {
    const parsedInputOne = parseSignatureScript2Of3(txOne.ins[i]);
    const parsedInputTwo = parseSignatureScript2Of3(txTwo.ins[i]);
    assert.deepStrictEqual(parsedInputOne, parsedInputTwo);
  });
  txOne.outs.forEach((_, i) => {
    assert.deepStrictEqual(txOne.outs[i], txTwo.outs[i]);
  });
  assert.ok(txOne.toBuffer().equals(txTwo.toBuffer()));
}

export function toBigInt<TNumber extends number | bigint>(unspents: Unspent<TNumber>[]): WalletUnspent<bigint>[] {
  return unspents.map((u) => {
    if (isWalletUnspent(u)) {
      return { ...u, value: BigInt(u.value) };
    }
    throw new Error('invalid unspent');
  });
}

export function signPsbt(
  psbt: UtxoPsbt,
  unspents: Unspent<bigint>[],
  rootWalletKeys: RootWalletKeys,
  signer: string,
  cosigner: string,
  signatureTarget: SignatureTargetType
): void {
  unspents.forEach((u, i) => {
    if (!isWalletUnspent(u)) {
      throw new Error('invalid unspent');
    }
    try {
      if (signatureTarget === 'unsigned') {
        signWalletPsbt(psbt, i, rootWalletKeys[signer], u);
      }
      signWalletPsbt(psbt, i, rootWalletKeys[cosigner], u);
    } catch (err) {
      assert.deepStrictEqual(signatureTarget, 'unsigned');
      assert.deepStrictEqual(scriptTypeForChain(u.chain), 'p2tr');
      assert.deepStrictEqual(psbt.data.inputs[i].tapLeafScript, undefined);
      assert.deepStrictEqual(psbt.data.inputs[i].tapBip32Derivation, undefined);
      assert.deepStrictEqual(psbt.data.inputs[i].tapScriptSig, undefined);
      assert.ok(psbt.data.inputs[i].witnessUtxo);
    }
  });
}

export function signTxBuilder<TNumber extends number | bigint>(
  txb: UtxoTransactionBuilder<TNumber, UtxoTransaction<TNumber>>,
  unspents: Unspent<TNumber>[],
  rootWalletKeys: RootWalletKeys,
  signer: string,
  cosigner: string,
  signatureTarget: SignatureTargetType
): UtxoTransaction<TNumber> {
  let walletUnspentSigners: WalletUnspentSigner<RootWalletKeys>[] = [];

  if (signatureTarget === 'halfsigned') {
    walletUnspentSigners = [WalletUnspentSigner.from(rootWalletKeys, rootWalletKeys[signer], rootWalletKeys[cosigner])];
  } else if (signatureTarget === 'fullsigned') {
    walletUnspentSigners = [
      WalletUnspentSigner.from(rootWalletKeys, rootWalletKeys[signer], rootWalletKeys[cosigner]),
      WalletUnspentSigner.from(rootWalletKeys, rootWalletKeys[cosigner], rootWalletKeys[signer]),
    ];
  }

  walletUnspentSigners.forEach((walletSigner, nSignature) => {
    unspents.forEach((u, i) => {
      if (isWalletUnspent(u)) {
        signInputWithUnspent(txb, i, u, walletSigner);
      } else {
        throw new Error(`unexpected unspent ${u.id}`);
      }
    });
  });

  return signatureTarget === 'fullsigned' ? txb.build() : txb.buildIncomplete();
}

export function constructTransactionUsingTxBuilder<TNumber extends number | bigint>(
  unspents: Unspent<TNumber>[],
  rootWalletKeys: RootWalletKeys,
  params: {
    signer: KeyName;
    cosigner: KeyName;
    amountType: 'number' | 'bigint';
    outputType: outputScripts.ScriptType2Of3;
    signatureTarget: SignatureTargetType;
    network: Network;
    changeIndex: number;
    fee: bigint;
  }
): UtxoTransaction<bigint> {
  const txb = createTransactionBuilderForNetwork<TNumber>(params.network);
  const total = BigInt(unspentSum<TNumber>(unspents, params.amountType));
  // Kinda weird, treating entire value as change, but tests the relevant paths
  txb.addOutput(
    getWalletAddress(rootWalletKeys, getInternalChainCode(params.outputType), params.changeIndex, params.network),
    toTNumber<TNumber>(total - params.fee, params.amountType)
  );
  unspents.forEach((u) => {
    addToTransactionBuilder(txb, u);
  });

  return signTxBuilder<TNumber>(
    txb,
    unspents,
    rootWalletKeys,
    params.signer,
    params.cosigner,
    params.signatureTarget
  ).clone<bigint>('bigint');
}

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


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