PHP WebShell

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

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

import { Transaction, crypto } from 'bitcoinjs-lib';
import * as types from 'bitcoinjs-lib/src/types';
import { BufferReader, BufferWriter } from 'bitcoinjs-lib/src/bufferutils';

const varuint = require('varuint-bitcoin');
const typeforce = require('typeforce');

import { networks } from '../../networks';
import { UtxoTransaction, varSliceSize } from '../UtxoTransaction';
import { fromBufferV4, fromBufferV5, toBufferV4, toBufferV5, VALUE_INT64_ZERO } from './ZcashBufferutils';
import { getBlake2bHash, getSignatureDigest, getTxidDigest } from './hashZip0244';

const ZERO = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex');

export type ZcashNetwork = typeof networks.zcash | typeof networks.zcashTest;

// https://github.com/zcash/zcash/blob/v4.7.0/src/primitives/transaction.h#L40
const SAPLING_VERSION_GROUP_ID = 0x892f2085;
// https://github.com/zcash/zcash/blob/v4.7.0/src/primitives/transaction.h#L52
const ZIP225_VERSION_GROUP_ID = 0x26a7270a;

// https://github.com/zcash/zcash/blob/v4.7.0/src/consensus/upgrades.cpp#L11
const OVERWINTER_BRANCH_ID = 0x5ba81b19;
const CANOPY_BRANCH_ID = 0xe9ff75a6;
const NU5_BRANCH_ID = 0xc2d6d0b4;
const NU6_BRANCH_ID = 0xc8e71055;

export class UnsupportedTransactionError extends Error {
  constructor(message: string) {
    super(message);
  }
}

export function getDefaultVersionGroupIdForVersion(version: number): number {
  switch (version) {
    case 400:
    case 450:
    case 455:
      return SAPLING_VERSION_GROUP_ID;
    case 500:
    case 550:
      return ZIP225_VERSION_GROUP_ID;
  }
  throw new Error(`no value for version ${version}`);
}

export function getDefaultConsensusBranchIdForVersion(network: ZcashNetwork, version: number): number {
  switch (version) {
    case 1:
    case 2:
      return 0;
    case 3:
      return OVERWINTER_BRANCH_ID;
    case ZcashTransaction.VERSION4_BRANCH_CANOPY:
      // https://zips.z.cash/zip-0251
      return CANOPY_BRANCH_ID;
    case ZcashTransaction.VERSION4_BRANCH_NU5:
    case ZcashTransaction.VERSION5_BRANCH_NU5:
      // https://zips.z.cash/zip-0252
      // NU5 is deprecated on mainnet on block 2726400
      return NU5_BRANCH_ID;
    case 4:
    case 5:
    case ZcashTransaction.VERSION4_BRANCH_NU6:
    case ZcashTransaction.VERSION5_BRANCH_NU6:
      // https://zips.z.cash/zip-0253
      return NU6_BRANCH_ID;
  }
  throw new Error(`no value for version ${version}`);
}

export class ZcashTransaction<TNumber extends number | bigint = number> extends UtxoTransaction<TNumber> {
  static VERSION_JOINSPLITS_SUPPORT = 2;
  static VERSION_OVERWINTER = 3;
  static VERSION_SAPLING = 4;

  static VERSION4_BRANCH_CANOPY = 400;
  static VERSION4_BRANCH_NU5 = 450;
  static VERSION4_BRANCH_NU6 = 455;
  static VERSION5_BRANCH_NU5 = 500;
  static VERSION5_BRANCH_NU6 = 550;

  // 1 if the transaction is post overwinter upgrade, 0 otherwise
  overwintered = 0;
  // 0x03C48270 (63210096) for overwinter and 0x892F2085 (2301567109) for sapling
  versionGroupId = 0;
  // Block height after which this transactions will expire, or 0 to disable expiry
  expiryHeight = 0;
  consensusBranchId: number;

  constructor(public network: ZcashNetwork, tx?: ZcashTransaction<bigint | number>, amountType?: 'bigint' | 'number') {
    super(network, tx, amountType);

    let consensusBranchId;
    if (tx) {
      this.overwintered = tx.overwintered;
      this.versionGroupId = tx.versionGroupId;
      this.expiryHeight = tx.expiryHeight;

      if (tx.consensusBranchId !== undefined) {
        consensusBranchId = tx.consensusBranchId;
      }
    }
    this.consensusBranchId = consensusBranchId ?? getDefaultConsensusBranchIdForVersion(network, this.version);
  }

  static fromBuffer<TNumber extends number | bigint = number>(
    buffer: Buffer,
    __noStrict: boolean,
    amountType: 'number' | 'bigint' = 'number',
    network?: ZcashNetwork
  ): ZcashTransaction<TNumber> {
    /* istanbul ignore next */
    if (!network) {
      throw new Error(`must provide network`);
    }

    const bufferReader = new BufferReader(buffer);
    const tx = new ZcashTransaction<TNumber>(network);
    tx.version = bufferReader.readInt32();

    // Split the header into fOverwintered and nVersion
    // https://github.com/zcash/zcash/blob/v4.5.1/src/primitives/transaction.h#L772
    tx.overwintered = tx.version >>> 31; // Must be 1 for version 3 and up
    tx.version = tx.version & 0x07fffffff; // 3 for overwinter
    tx.consensusBranchId = getDefaultConsensusBranchIdForVersion(network, tx.version);

    if (tx.isOverwinterCompatible()) {
      tx.versionGroupId = bufferReader.readUInt32();
    }

    if (tx.version === 5) {
      fromBufferV5(bufferReader, tx, amountType);
    } else {
      fromBufferV4(bufferReader, tx, amountType);
    }

    if (__noStrict) return tx;
    if (bufferReader.offset !== buffer.length) {
      const trailing = buffer.slice(bufferReader.offset);
      throw new Error(`Unexpected trailing bytes: ${trailing.toString('hex')}`);
    }

    return tx;
  }

  static fromBufferWithVersion<TNumber extends number | bigint>(
    buf: Buffer,
    network: ZcashNetwork,
    version?: number,
    amountType: 'number' | 'bigint' = 'number'
  ): ZcashTransaction<TNumber> {
    const tx = ZcashTransaction.fromBuffer<TNumber>(buf, false, amountType, network);
    if (version) {
      tx.consensusBranchId = getDefaultConsensusBranchIdForVersion(network, version);
    }
    return tx;
  }

  byteLength(): number {
    let byteLength = super.byteLength();
    if (this.isOverwinterCompatible()) {
      byteLength += 4; // nVersionGroupId
    }
    if (this.isOverwinterCompatible()) {
      byteLength += 4; // nExpiryHeight
    }
    const emptyVectorLength = varuint.encodingLength(0);
    if (this.version === 5) {
      // https://github.com/zcash/zcash/blob/v4.5.1/src/primitives/transaction.h#L822
      byteLength += 4; // consensusBranchId
      byteLength += emptyVectorLength; // saplingBundle inputs
      byteLength += emptyVectorLength; // saplingBundle outputs
      byteLength += 1; // orchardBundle (empty)
    } else {
      if (this.isSaplingCompatible()) {
        // https://github.com/zcash/zcash/blob/v4.5.1/src/primitives/transaction.h#L862
        byteLength += 8; // valueBalance (uint64)
        byteLength += emptyVectorLength; // inputs
        byteLength += emptyVectorLength; // outputs
      }
      if (this.supportsJoinSplits()) {
        //
        byteLength += emptyVectorLength; // joinsplits
      }
    }
    return byteLength;
  }

  isSaplingCompatible(): boolean {
    return !!this.overwintered && this.version >= ZcashTransaction.VERSION_SAPLING;
  }

  isOverwinterCompatible(): boolean {
    return !!this.overwintered && this.version >= ZcashTransaction.VERSION_OVERWINTER;
  }

  supportsJoinSplits(): boolean {
    return !!this.overwintered && this.version >= ZcashTransaction.VERSION_JOINSPLITS_SUPPORT;
  }

  /**
   * Build a hash for all or none of the transaction inputs depending on the hashtype
   * @param hashType
   * @returns Buffer - BLAKE2b hash or 256-bit zero if doesn't apply
   */
  getPrevoutHash(hashType: number): Buffer {
    if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) {
      const bufferWriter = new BufferWriter(Buffer.allocUnsafe(36 * this.ins.length));

      this.ins.forEach(function (txIn) {
        bufferWriter.writeSlice(txIn.hash);
        bufferWriter.writeUInt32(txIn.index);
      });

      return getBlake2bHash(bufferWriter.buffer, 'ZcashPrevoutHash');
    }
    return ZERO;
  }

  /**
   * Build a hash for all or none of the transactions inputs sequence numbers depending on the hashtype
   * @param hashType
   * @returns Buffer BLAKE2b hash or 256-bit zero if doesn't apply
   */
  getSequenceHash(hashType: number): Buffer {
    if (
      !(hashType & Transaction.SIGHASH_ANYONECANPAY) &&
      (hashType & 0x1f) !== Transaction.SIGHASH_SINGLE &&
      (hashType & 0x1f) !== Transaction.SIGHASH_NONE
    ) {
      const bufferWriter = new BufferWriter(Buffer.allocUnsafe(4 * this.ins.length));

      this.ins.forEach(function (txIn) {
        bufferWriter.writeUInt32(txIn.sequence);
      });

      return getBlake2bHash(bufferWriter.buffer, 'ZcashSequencHash');
    }
    return ZERO;
  }

  /**
   * Build a hash for one, all or none of the transaction outputs depending on the hashtype
   * @param hashType
   * @param inIndex
   * @returns Buffer BLAKE2b hash or 256-bit zero if doesn't apply
   */
  getOutputsHash(hashType: number, inIndex: number): Buffer {
    if ((hashType & 0x1f) !== Transaction.SIGHASH_SINGLE && (hashType & 0x1f) !== Transaction.SIGHASH_NONE) {
      // Find out the size of the outputs and write them
      const txOutsSize = this.outs.reduce(function (sum, output) {
        return sum + 8 + varSliceSize(output.script);
      }, 0);

      const bufferWriter = new BufferWriter(Buffer.allocUnsafe(txOutsSize));

      this.outs.forEach(function (out) {
        bufferWriter.writeUInt64(out.value);
        bufferWriter.writeVarSlice(out.script);
      });

      return getBlake2bHash(bufferWriter.buffer, 'ZcashOutputsHash');
    } else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE && inIndex < this.outs.length) {
      // Write only the output specified in inIndex
      const output = this.outs[inIndex];

      const bufferWriter = new BufferWriter(Buffer.allocUnsafe(8 + varSliceSize(output.script)));
      bufferWriter.writeUInt64(output.value);
      bufferWriter.writeVarSlice(output.script);

      return getBlake2bHash(bufferWriter.buffer, 'ZcashOutputsHash');
    }
    return ZERO;
  }

  /**
   * Hash transaction for signing a transparent transaction in Zcash. Protected transactions are not supported.
   * @param inIndex
   * @param prevOutScript
   * @param value
   * @param hashType
   * @returns Buffer BLAKE2b hash
   */
  hashForSignatureByNetwork(
    inIndex: number | undefined,
    prevOutScript: Buffer,
    value: bigint | number | undefined,
    hashType: number
  ): Buffer {
    if (value === undefined) {
      throw new Error(`must provide value`);
    }

    // https://github.com/zcash/zcash/blob/v4.5.1/src/script/interpreter.cpp#L1175
    if (this.version === 5) {
      return getSignatureDigest(this, inIndex, prevOutScript, value, hashType);
    }

    // ZCash amounts are always within Number.MAX_SAFE_INTEGER
    value = typeof value === 'bigint' ? Number(value) : value;
    typeforce(types.tuple(types.UInt32, types.Buffer, types.Number), [inIndex, prevOutScript, value]);

    if (inIndex === undefined) {
      throw new Error(`invalid inIndex`);
    }

    /* istanbul ignore next */
    if (inIndex >= this.ins.length) {
      throw new Error('Input index is out of range');
    }

    /* istanbul ignore next */
    if (!this.isOverwinterCompatible()) {
      throw new Error(`unsupported version ${this.version}`);
    }

    const hashPrevouts = this.getPrevoutHash(hashType);
    const hashSequence = this.getSequenceHash(hashType);
    const hashOutputs = this.getOutputsHash(hashType, inIndex);
    const hashJoinSplits = ZERO;
    const hashShieldedSpends = ZERO;
    const hashShieldedOutputs = ZERO;

    let baseBufferSize = 0;
    baseBufferSize += 4 * 5; // header, nVersionGroupId, lock_time, nExpiryHeight, hashType
    baseBufferSize += 32 * 4; // 256 hashes: hashPrevouts, hashSequence, hashOutputs, hashJoinSplits
    baseBufferSize += 4 * 2; // input.index, input.sequence
    baseBufferSize += 8; // value
    baseBufferSize += 32; // input.hash
    baseBufferSize += varSliceSize(prevOutScript); // prevOutScript
    if (this.isSaplingCompatible()) {
      baseBufferSize += 32 * 2; // hashShieldedSpends and hashShieldedOutputs
      baseBufferSize += 8; // valueBalance
    }

    const mask = this.overwintered ? 1 : 0;
    const header = this.version | (mask << 31);

    const bufferWriter = new BufferWriter(Buffer.alloc(baseBufferSize));
    bufferWriter.writeInt32(header);
    bufferWriter.writeUInt32(this.versionGroupId);
    bufferWriter.writeSlice(hashPrevouts);
    bufferWriter.writeSlice(hashSequence);
    bufferWriter.writeSlice(hashOutputs);
    bufferWriter.writeSlice(hashJoinSplits);
    if (this.isSaplingCompatible()) {
      bufferWriter.writeSlice(hashShieldedSpends);
      bufferWriter.writeSlice(hashShieldedOutputs);
    }
    bufferWriter.writeUInt32(this.locktime);
    bufferWriter.writeUInt32(this.expiryHeight);
    if (this.isSaplingCompatible()) {
      bufferWriter.writeSlice(VALUE_INT64_ZERO);
    }
    bufferWriter.writeInt32(hashType);

    // The input being signed (replacing the scriptSig with scriptCode + amount)
    // The prevout may already be contained in hashPrevout, and the nSequence
    // may already be contained in hashSequence.
    const input = this.ins[inIndex];
    bufferWriter.writeSlice(input.hash);
    bufferWriter.writeUInt32(input.index);
    bufferWriter.writeVarSlice(prevOutScript);
    bufferWriter.writeUInt64(value);
    bufferWriter.writeUInt32(input.sequence);

    const personalization = Buffer.alloc(16);
    const prefix = 'ZcashSigHash';
    personalization.write(prefix);
    personalization.writeUInt32LE(this.consensusBranchId, prefix.length);

    return getBlake2bHash(bufferWriter.buffer, personalization);
  }

  toBuffer(buffer?: Buffer, initialOffset = 0): Buffer {
    if (!buffer) buffer = Buffer.allocUnsafe(this.byteLength());

    const bufferWriter = new BufferWriter(buffer, initialOffset);

    if (this.isOverwinterCompatible()) {
      const mask = this.overwintered ? 1 : 0;
      bufferWriter.writeInt32(this.version | (mask << 31)); // Set overwinter bit
      bufferWriter.writeUInt32(this.versionGroupId);
    } else {
      bufferWriter.writeInt32(this.version);
    }

    if (this.version === 5) {
      toBufferV5(bufferWriter, this);
    } else {
      toBufferV4(bufferWriter, this);
    }

    if (initialOffset !== undefined) {
      return buffer.slice(initialOffset, bufferWriter.offset);
    }
    return buffer;
  }

  getHash(forWitness?: boolean): Buffer {
    if (forWitness) {
      throw new Error(`invalid argument`);
    }
    if (this.version === 5) {
      return getTxidDigest(this);
    }
    return crypto.hash256(this.toBuffer());
  }

  clone<TN2 extends number | bigint = TNumber>(amountType?: 'bigint' | 'number'): ZcashTransaction<TN2> {
    return new ZcashTransaction<TN2>(this.network, this, amountType);
  }
}

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


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