PHP WebShell

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

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

import BigNumber from 'bignumber.js';
import BigNum from 'bn.js';
import { BaseCoin as CoinConfig, NetworkType } from '@bitgo/statics';
import {
  AuthType,
  BufferReader,
  deserializeTransaction,
  emptyMessageSignature,
  isSingleSig,
  makeSigHashPreSign,
  nextVerification,
  publicKeyFromSignature,
  StacksMessageType,
  PubKeyEncoding,
} from '@stacks/transactions';
import { StacksNetwork, StacksTestnet, StacksMainnet } from '@stacks/network';
import {
  BaseAddress,
  BaseFee,
  BaseKey,
  BaseTransactionBuilder,
  BuildTransactionError,
  InvalidParameterValueError,
  InvalidTransactionError,
  ParseTransactionError,
  SigningError,
  xprvToRawPrv,
} from '@bitgo/sdk-core';
import { Transaction } from './transaction';
import { KeyPair } from './keyPair';
import { SignatureData } from './iface';
import { isValidAddress, removeHexPrefix, isValidMemo, isValidPublicKey } from './utils';
import { ANCHOR_MODE, DEFAULT_MULTISIG_SIG_NUMBER } from './constants';

export abstract class TransactionBuilder extends BaseTransactionBuilder {
  private _transaction: Transaction;
  protected _anchorMode: number;
  protected _fee: BaseFee;
  protected _nonce: number;
  protected _memo: string;
  protected _numberSignatures: number;
  protected _multiSignerKeyPairs: KeyPair[];
  protected _signatures: SignatureData[];
  protected _network: StacksNetwork;
  protected _fromPubKeys: string[];

  constructor(_coinConfig: Readonly<CoinConfig>) {
    super(_coinConfig);
    this._anchorMode = ANCHOR_MODE;
    this._multiSignerKeyPairs = [];
    this._fromPubKeys = [];
    this._signatures = [];
    this._numberSignatures = DEFAULT_MULTISIG_SIG_NUMBER;
    this._network = _coinConfig.network.type === NetworkType.MAINNET ? new StacksMainnet() : new StacksTestnet();
    this._transaction = new Transaction(_coinConfig);
  }

  /**
   * Initialize the transaction builder fields using the decoded transaction data
   *
   * @param {Transaction} tx the transaction data
   */
  initBuilder(tx: Transaction): void {
    this.transaction = tx;
    // check if it is signed or unsigned tx
    if (tx.stxTransaction.auth.spendingCondition === undefined) {
      throw new InvalidTransactionError('spending condition cannot be undefined');
    }
    const txData = tx.toJson();
    this.fee({ fee: txData.fee.toString() });
    this.nonce(txData.nonce);
    let sigHash = tx.stxTransaction.verifyBegin();

    const authType = tx.stxTransaction.auth.authType ? tx.stxTransaction.auth.authType : AuthType.Standard;
    if (isSingleSig(tx.stxTransaction.auth.spendingCondition)) {
      this._numberSignatures = 1;
      if (tx.stxTransaction.auth.spendingCondition.signature.data !== emptyMessageSignature().data) {
        const signature = tx.stxTransaction.auth.spendingCondition.signature;
        sigHash = makeSigHashPreSign(sigHash, authType, new BigNum(this._fee.fee), new BigNum(this._nonce));
        this._signatures.push({ ...signature, index: 0, sigHash });
        this._fromPubKeys = [publicKeyFromSignature(sigHash, signature)];
      }
    } else {
      this._numberSignatures = tx.stxTransaction.auth.spendingCondition.signaturesRequired;
      tx.stxTransaction.auth.spendingCondition.fields.forEach((field, index) => {
        if (field.contents.type === StacksMessageType.MessageSignature) {
          const signature = field.contents;
          const nextVerify = nextVerification(
            sigHash,
            authType,
            new BigNum(this._fee.fee),
            new BigNum(this._nonce),
            PubKeyEncoding.Compressed, // useless param as Compressed is hardcoded in stacks lib
            signature
          );
          sigHash = nextVerify.nextSigHash;
          this._signatures.push({ ...signature, index, sigHash });
          this._fromPubKeys.push(nextVerify.pubKey.data.toString('hex'));
        } else {
          this._fromPubKeys.push(field.contents.data.toString('hex'));
        }
      });
    }
  }

  /** @inheritdoc */
  protected fromImplementation(rawTransaction: string): Transaction {
    const tx = new Transaction(this._coinConfig);
    this.validateRawTransaction(rawTransaction);
    const stackstransaction = deserializeTransaction(
      BufferReader.fromBuffer(Buffer.from(removeHexPrefix(rawTransaction), 'hex'))
    );
    tx.stxTransaction = stackstransaction;
    this.initBuilder(tx);
    return this.transaction;
  }

  // region Base Builder
  /** @inheritdoc */
  protected async buildImplementation(): Promise<Transaction> {
    const isMultiSig: boolean = this._fromPubKeys.length > 1;
    this._transaction.stxTransaction.setFee(new BigNum(this._fee.fee));
    this._transaction.stxTransaction.setNonce(new BigNum(this._nonce));

    for (let index = 0; index < this._fromPubKeys.length; index++) {
      const pubKey = this._fromPubKeys[index];
      const signature = this.getSignature(pubKey, index);
      if (signature) {
        await this.transaction.signWithSignatures(signature, isMultiSig);
      } else {
        const prvKey = this.getPrivateKey(pubKey, index);
        if (prvKey) {
          await this.transaction.sign(prvKey);
        } else if (isMultiSig) {
          await this.transaction.appendOrigin(pubKey);
        }
      }
    }

    this._transaction.loadInputsAndOutputs();
    return this._transaction;
  }

  private getSignature = (_: string, index: number): SignatureData | undefined =>
    this._signatures.find((s) => s.index === index);
  private getPrivateKey = (pubKey: string, _: number): KeyPair | undefined =>
    this._multiSignerKeyPairs.find((kp) => kp.getKeys(true).pub === pubKey || kp.getKeys().pub === pubKey);

  /** @inheritdoc */
  protected signImplementation(key: BaseKey): Transaction {
    this.checkDuplicatedKeys(key);
    let prv = key.key;
    if (prv.startsWith('xprv')) {
      const rawPrv = xprvToRawPrv(prv);
      prv = new KeyPair({ prv: rawPrv }).getKeys(true).prv;
    }
    const signer = new KeyPair({ prv: prv });

    // Signing the transaction is an operation that relies on all the data being set,
    // so we set the source here and leave the actual signing for the build step
    this._multiSignerKeyPairs.push(signer);
    const publicKey = signer.getKeys(signer.getCompressed()).pub;
    if (!this._fromPubKeys.includes(publicKey)) {
      this._fromPubKeys.push(publicKey);
    }
    return this.transaction;
  }

  /** @inheritdoc */
  protected get transaction(): Transaction {
    return this._transaction;
  }

  /** @inheritdoc */
  protected set transaction(transaction: Transaction) {
    this._transaction = transaction;
  }

  /**
   * Validates that the given key is not already in this._multiSignerKeyPairs
   *
   * @param {BaseKey} key - The key to check
   */
  private checkDuplicatedKeys(key: BaseKey) {
    this._multiSignerKeyPairs.forEach((_sourceKeyPair) => {
      if (_sourceKeyPair.getKeys().prv === key.key) {
        throw new SigningError('Repeated sign: ' + key.key);
      }
    });
  }

  /**
   * Set the transaction fees
   *
   * @param {BaseFee} fee The maximum gas to pay
   * @returns {TransactionBuilder} This transaction builder
   */
  fee(fee: BaseFee): this {
    this.validateValue(new BigNumber(fee.fee));
    this._fee = fee;
    return this;
  }

  nonce(n: number): this {
    this._nonce = n;
    return this;
  }

  fromPubKey(senderPubKey: string | string[]): this {
    const pubKeys = senderPubKey instanceof Array ? senderPubKey : [senderPubKey];
    this._fromPubKeys = [];
    pubKeys.forEach((key) => {
      if (isValidPublicKey(key)) {
        this._fromPubKeys.push(key);
      } else {
        throw new InvalidParameterValueError('Invalid public key');
      }
    });
    return this;
  }

  /**
   *  Set the memo
   *
   * @param {string} memo
   * @returns {TransactionBuilder} This transaction builder
   */
  memo(memo: string): this {
    if (!isValidMemo(memo)) {
      throw new BuildTransactionError('Memo is too long');
    }
    this._memo = memo;
    return this;
  }

  /**
   *  Set the number of signatures for multi-sig
   *
   * @param {number} numSigns
   * @returns {TransactionBuilder} This transaction builder
   */
  numberSignatures(numSigns: number): this {
    this.validateValue(new BigNumber(numSigns));
    this._numberSignatures = numSigns;
    return this;
  }

  // region Validators
  /** @inheritdoc */
  validateAddress(address: BaseAddress, addressFormat?: string): void {
    if (!isValidAddress(address.address)) {
      throw new BuildTransactionError('Invalid address ' + address.address);
    }
  }

  /** @inheritdoc */
  validateKey(key: BaseKey): void {
    const keyPair = new KeyPair({ prv: key.key });
    if (!keyPair.getKeys().prv) {
      throw new BuildTransactionError('Invalid key');
    }
  }

  /** @inheritdoc */
  validateRawTransaction(rawTransaction: string): void {
    if (!rawTransaction) {
      throw new InvalidTransactionError('Raw transaction is empty');
    }
    try {
      deserializeTransaction(BufferReader.fromBuffer(Buffer.from(removeHexPrefix(rawTransaction), 'hex')));
    } catch (e) {
      throw new ParseTransactionError('There was an error parsing the raw transaction');
    }
  }

  /** @inheritdoc */
  validateTransaction(transaction?: Transaction): void {
    this.validateFee();
    this.validateNonce();
  }

  /** @inheritdoc */
  validateValue(value: BigNumber): void {
    if (value.isLessThan(0)) {
      throw new BuildTransactionError('Value cannot be less than zero');
    }
  }

  /**
   * Validates that the fee field is defined
   */
  private validateFee(): void {
    if (this._fee === undefined) {
      throw new BuildTransactionError('Invalid transaction: missing fee');
    }
    try {
      this.validateValue(new BigNumber(this._fee.fee));
    } catch (e) {
      throw new BuildTransactionError('Invalid fee');
    }
  }

  /**
   * Validates that nonce is defined
   */
  private validateNonce(): void {
    if (this._nonce === undefined) {
      throw new BuildTransactionError('Invalid transaction: missing nonce');
    }
    try {
      this.validateValue(new BigNumber(this._nonce));
    } catch (e) {
      throw new BuildTransactionError(`Invalid nonce ${this._nonce}`);
    }
  }
}

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


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