PHP WebShell

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

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

import * as assert from 'assert';
import { describe, it } from 'mocha';
import { BIP32Interface } from 'bip32';

import { isTestnet, TxOutput, getNetworkList, getNetworkName, networks } from '../../src';
import { getDefaultCosigner } from '../../src/testutil';

import {
  createTransactionBuilderForNetwork,
  createTransactionBuilderFromTransaction,
  createTransactionFromBuffer,
  getDefaultTransactionVersion,
  getOutputIdForInput,
  ParsedSignatureScriptP2ms,
  parseSignatureScript,
  parseSignatureScript2Of3,
  signInput2Of3,
  Triple,
  TxOutPoint,
  UtxoTransaction,
  verifySignature,
} from '../../src/bitgo';
import { isScriptType2Of3, ScriptType2Of3 } from '../../src/bitgo/outputScripts';

import {
  createSpendTransactionFromPrevOutputs,
  isSupportedDepositType,
  isSupportedSpendType,
  ScriptType,
  scriptTypes,
} from './generate/outputScripts.util';
import {
  fixtureKeys,
  getProtocolVersions,
  Protocol,
  readFixture,
  TransactionFixtureWithInputs,
} from './generate/fixtures';
import { parseTransactionRoundTrip } from '../transaction_util';
import { normalizeParsedTransaction, normalizeRpcTransaction } from './compare';
import { decimalCoinsToSats } from '../testutil';

const fixtureTxTypes = ['deposit', 'spend'] as const;
type FixtureTxType = (typeof fixtureTxTypes)[number];

function getScriptTypes() {
  // FIXME(BG-66941): p2trMusig2 signing does not work in this test suite yet
  //  because the test suite is written with TransactionBuilder
  return scriptTypes.filter((scriptType) => scriptType !== 'p2trMusig2');
}

function runTestParse<TNumber extends number | bigint>(
  protocol: Protocol,
  txType: FixtureTxType,
  scriptType: ScriptType,
  amountType: 'number' | 'bigint'
) {
  if (txType === 'deposit' && !isSupportedDepositType(protocol.network, scriptType)) {
    return;
  }

  if (txType === 'spend' && !isSupportedSpendType(protocol.network, scriptType)) {
    return;
  }

  const fixtureName = `${txType}_${scriptType}.json`;
  describe(`${fixtureName} amountType=${amountType}`, function () {
    let fixture: TransactionFixtureWithInputs;
    let txBuffer: Buffer;
    let parsedTx: UtxoTransaction<TNumber>;

    before(async function () {
      fixture = await readFixture(
        {
          network: protocol.network,
          version: protocol.version ?? getDefaultTransactionVersion(protocol.network),
        },
        fixtureName
      );
      txBuffer = Buffer.from(fixture.transaction.hex, 'hex');
      parsedTx = createTransactionFromBuffer<TNumber>(txBuffer, protocol.network, {
        version: protocol.version,
        amountType,
      });
    });

    type InputLookup = { txid?: string; hash?: Buffer; index: number };

    function getPrevOutput(input: InputLookup) {
      if (input.hash) {
        input = {
          ...input,
          ...getOutputIdForInput(input as { hash: Buffer; index: number }),
        };
      }

      const inputTx = fixture.inputs.find((tx) => tx.txid === input.txid);
      if (!inputTx) {
        throw new Error(`could not find inputTx`);
      }
      const prevOutput = inputTx.vout[input.index];
      if (!prevOutput) {
        throw new Error(`could not prevOutput`);
      }
      return prevOutput;
    }

    function getPrevOutputValue(input: InputLookup): TNumber {
      return decimalCoinsToSats<TNumber>(getPrevOutput(input).value, amountType);
    }

    function getPrevOutputScript(input: InputLookup): Buffer {
      return Buffer.from(getPrevOutput(input).scriptPubKey.hex, 'hex');
    }

    function getPrevOutputs(): (TxOutPoint & TxOutput<TNumber>)[] {
      return parsedTx.ins.map((i) => ({
        ...getOutputIdForInput(i),
        script: getPrevOutputScript(i),
        value: getPrevOutputValue(i),
        prevTx: txBuffer,
      }));
    }

    it(`round-trip`, function () {
      parseTransactionRoundTrip(Buffer.from(fixture.transaction.hex, 'hex'), protocol.network, {
        inputs: getPrevOutputs(),
        amountType,
        version: protocol.version,
        // FIXME: prevTx parsing for Zcash not working yet
        roundTripPsbt: txType === 'spend' && protocol.network !== networks.zcashTest,
      });
    });

    it(`round-trip (high-precision values)`, function () {
      if (amountType !== 'bigint') {
        return;
      }
      const tx = createTransactionFromBuffer<TNumber>(Buffer.from(fixture.transaction.hex, 'hex'), protocol.network, {
        amountType,
      });
      tx.outs.forEach((o) => {
        o.value = (BigInt(1e16) + BigInt(1)) as TNumber;
        assert.notStrictEqual(BigInt(Number(o.value)), o.value);
      });
      const txRoundTrip = parseTransactionRoundTrip(tx.toBuffer(), protocol.network, { amountType });
      assert.strictEqual(txRoundTrip.outs.length, tx.outs.length);
      txRoundTrip.outs.forEach((o, i) => {
        assert.deepStrictEqual(o, tx.outs[i]);
      });
    });

    it(`recreate from unsigned hex`, function () {
      if (txType === 'deposit') {
        return;
      }
      const txbUnsigned = createTransactionBuilderForNetwork<TNumber>(protocol.network, { version: protocol.version });
      getPrevOutputs().forEach((o) => {
        txbUnsigned.addInput(o.txid, o.vout);
      });
      fixture.transaction.vout.forEach((o) => {
        txbUnsigned.addOutput(Buffer.from(o.scriptPubKey.hex, 'hex'), decimalCoinsToSats<TNumber>(o.value, amountType));
      });

      const tx = createTransactionFromBuffer<TNumber>(txbUnsigned.buildIncomplete().toBuffer(), protocol.network, {
        version: protocol.version,
        amountType,
      });
      const txb = createTransactionBuilderFromTransaction(tx, getPrevOutputs());
      const signKeys = [fixtureKeys[0], fixtureKeys[2]];
      const publicKeys = fixtureKeys.map((k) => k.publicKey) as Triple<Buffer>;
      getPrevOutputs().forEach(({ value }, vin) => {
        signKeys.forEach((key) => {
          signInput2Of3(
            txb,
            vin,
            scriptType as ScriptType2Of3,
            publicKeys,
            key,
            getDefaultCosigner(publicKeys, key.publicKey),
            value
          );
        });
      });

      assert.strictEqual(txb.build().version, tx.version);

      assert.strictEqual(txb.build().toBuffer().toString('hex'), fixture.transaction.hex);
    });

    it('compare against RPC data', function () {
      assert.deepStrictEqual(
        normalizeRpcTransaction(fixture.transaction, protocol.network),
        normalizeParsedTransaction(parsedTx, protocol.network)
      );
    });

    it(`parseSignatureScript`, function () {
      if (txType === 'deposit') {
        return;
      }

      parsedTx.ins.forEach((input, i) => {
        const result = parseSignatureScript(input) as ParsedSignatureScriptP2ms;

        assert.strict(result.publicKeys !== undefined);
        assert.strictEqual(result.publicKeys.length, scriptType === 'p2tr' ? 2 : 3);
      });
    });

    if (txType === 'deposit') {
      return;
    }

    it(`verifySignatures for original transaction`, function () {
      parsedTx.ins.forEach((input, i) => {
        const prevOutValue = getPrevOutputValue(input);
        const result = parseSignatureScript2Of3(input);
        assert.ok(result.scriptType !== 'taprootKeyPathSpend');
        if (!result.publicKeys) {
          throw new Error(`expected publicKeys`);
        }
        assert.strictEqual(result.publicKeys.length, scriptType === 'p2tr' ? 2 : 3);

        if (scriptType === 'p2tr') {
          // TODO implement verifySignature for p2tr
          this.skip();
        }

        result.publicKeys.forEach((publicKey, publicKeyIndex) => {
          assert.strictEqual(
            verifySignature(parsedTx, i, prevOutValue, {
              publicKey,
            }),
            publicKeyIndex === 0 || publicKeyIndex === 2
          );
        });

        assert.strictEqual(verifySignature(parsedTx, i, prevOutValue), true);
      });
    });

    function getRebuiltTransaction(signKeys?: BIP32Interface[]) {
      assert.strict(parsedTx.outs.length === 1);
      assert.strict(isScriptType2Of3(scriptType));
      const recipientScript = parsedTx.outs[0].script;
      return createSpendTransactionFromPrevOutputs(
        fixtureKeys,
        scriptType,
        getPrevOutputs(),
        recipientScript,
        protocol.network,
        { signKeys, version: protocol.version }
      );
    }

    it(`verifySignatures with one or two signatures`, function () {
      fixtureKeys.forEach((key1) => {
        const rebuiltTx = getRebuiltTransaction([key1]);
        const prevOutputs = rebuiltTx.ins.map((v) => ({
          script: getPrevOutputScript(v),
          value: getPrevOutputValue(v),
        }));
        rebuiltTx.ins.forEach((input, i) => {
          assert.strict(verifySignature(rebuiltTx, i, getPrevOutputValue(input), {}, prevOutputs));
        });

        fixtureKeys.forEach((key2) => {
          if (key1 === key2) {
            return;
          }

          if (scriptType === 'p2tr') {
            const keypair = [fixtureKeys[0], fixtureKeys[2]];
            if (!keypair.includes(key1) || !keypair.includes(key2)) {
              return;
            }
          }

          const rebuiltTx = getRebuiltTransaction([key1, key2]);
          rebuiltTx.ins.forEach((input, i) => {
            assert.strict(verifySignature(rebuiltTx, i, getPrevOutputValue(input), {}, prevOutputs));
          });
        });
      });
    });

    it('createSpendTransaction match', function () {
      const rebuiltTx = getRebuiltTransaction();
      assert.strictEqual(rebuiltTx.toBuffer().toString('hex'), fixture.transaction.hex);
    });
  });
}

describe(`regtest fixtures`, function () {
  getNetworkList().forEach((network) => {
    if (!isTestnet(network)) {
      return;
    }

    const allVersions = getProtocolVersions(network);
    it('tests default version', function () {
      // FIXME(BTC-1633): fix generating fixtures for version 455 NU6
      if (networks.zcashTest === network) {
        assert.strictEqual(getDefaultTransactionVersion(network), 455);
      } else {
        assert.strictEqual(allVersions.filter((v) => v === getDefaultTransactionVersion(network)).length, 1);
      }
    });

    getProtocolVersions(network).forEach((version) => {
      const isDefault = version === getDefaultTransactionVersion(network);
      describe(`${getNetworkName(network)} fixtures (version=${version}, isDefault=${isDefault})`, function () {
        getScriptTypes().forEach((scriptType) => {
          fixtureTxTypes.forEach((txType) => {
            runTestParse(
              { network, version },
              txType,
              scriptType,
              network === networks.dogecoinTest ? 'bigint' : 'number'
            );
          });
        });
      });
    });
  });
});

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


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