PHP WebShell

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

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

import { BIP32Interface } from 'bip32';

import { Transaction, taproot, TxOutput, ScriptSignature } from 'bitcoinjs-lib';

import { UtxoTransaction } from './UtxoTransaction';
import { UtxoTransactionBuilder } from './UtxoTransactionBuilder';
import {
  createOutputScript2of3,
  createOutputScriptP2shP2pk,
  createSpendScriptP2tr,
  getOutputScript,
  ScriptType,
  ScriptType2Of3,
  scriptType2Of3AsPrevOutType,
} from './outputScripts';
import { Triple } from './types';
import { getMainnet, Network, networks } from '../networks';
import { ecc as eccLib } from '../noble_ecc';
import { parseSignatureScript2Of3 } from './parseInput';
import { getTaprootOutputKey } from '../taproot';

/**
 * Constraints for signature verifications.
 * Parameters are conjunctive: if multiple parameters are set, a verification for an individual
 * signature must satisfy all of them.
 */
export type VerificationSettings = {
  /**
   * The index of the signature to verify. Only iterates over non-empty signatures.
   */
  signatureIndex?: number;
  /**
   * The public key to verify.
   */
  publicKey?: Buffer;
};

/**
 * Result for a individual signature verification
 */
export type SignatureVerification =
  | {
      /** Set to the public key that signed for the signature */
      signedBy: Buffer;
      /** Set to the signature buffer */
      signature: Buffer;
    }
  | { signedBy: undefined; signature: undefined };

/**
 * @deprecated - use {@see verifySignaturesWithPublicKeys} instead
 * Get signature verifications for multsig transaction
 * @param transaction
 * @param inputIndex
 * @param amount - must be set for segwit transactions and BIP143 transactions
 * @param verificationSettings
 * @param prevOutputs - must be set for p2tr and p2trMusig2 transactions
 * @returns SignatureVerification[] - in order of parsed non-empty signatures
 */
export function getSignatureVerifications<TNumber extends number | bigint>(
  transaction: UtxoTransaction<TNumber>,
  inputIndex: number,
  amount: TNumber,
  verificationSettings: VerificationSettings = {},
  prevOutputs?: TxOutput<TNumber>[]
): SignatureVerification[] {
  /* istanbul ignore next */
  if (!transaction.ins) {
    throw new Error(`invalid transaction`);
  }

  const input = transaction.ins[inputIndex];
  /* istanbul ignore next */
  if (!input) {
    throw new Error(`no input at index ${inputIndex}`);
  }

  if ((!input.script || input.script.length === 0) && input.witness.length === 0) {
    // Unsigned input: no signatures.
    return [];
  }

  const parsedScript = parseSignatureScript2Of3(input);

  if (parsedScript.scriptType === 'taprootKeyPathSpend' || parsedScript.scriptType === 'taprootScriptPathSpend') {
    if (
      parsedScript.scriptType === 'taprootKeyPathSpend' &&
      (verificationSettings.signatureIndex || verificationSettings.publicKey)
    ) {
      throw new Error(`signatureIndex and publicKey parameters not supported for taprootKeyPathSpend`);
    }

    if (verificationSettings.signatureIndex !== undefined) {
      throw new Error(`signatureIndex parameter not supported for taprootScriptPathSpend`);
    }

    if (!prevOutputs) {
      throw new Error(`prevOutputs not set`);
    }

    if (prevOutputs.length !== transaction.ins.length) {
      throw new Error(`prevOutputs length ${prevOutputs.length}, expected ${transaction.ins.length}`);
    }
  }

  if (
    parsedScript.scriptType !== 'taprootKeyPathSpend' &&
    parsedScript.scriptType !== 'taprootScriptPathSpend' &&
    prevOutputs
  ) {
    const prevOutScript = prevOutputs[inputIndex].script;

    const output = getOutputScript(parsedScript.scriptType, parsedScript.pubScript);
    if (!prevOutScript.equals(output)) {
      throw new Error(
        `prevout script ${prevOutScript.toString('hex')} does not match computed script ${output.toString('hex')}`
      );
    }
  }

  let publicKeys: Buffer[];
  if (parsedScript.scriptType === 'taprootKeyPathSpend') {
    if (!prevOutputs) {
      throw new Error(`prevOutputs not set`);
    }
    publicKeys = [getTaprootOutputKey(prevOutputs[inputIndex].script)];
  } else {
    publicKeys = parsedScript.publicKeys.filter(
      (buf) =>
        verificationSettings.publicKey === undefined ||
        verificationSettings.publicKey.equals(buf) ||
        verificationSettings.publicKey.slice(1).equals(buf)
    );
  }

  const signatures = parsedScript.signatures
    .filter((s) => s && s.length)
    .filter((s, i) => verificationSettings.signatureIndex === undefined || verificationSettings.signatureIndex === i);

  return signatures.map((signatureBuffer): SignatureVerification => {
    if (signatureBuffer === 0 || signatureBuffer.length === 0) {
      return { signedBy: undefined, signature: undefined };
    }

    let hashType = Transaction.SIGHASH_DEFAULT;

    if (signatureBuffer.length === 65) {
      hashType = signatureBuffer[signatureBuffer.length - 1];
      signatureBuffer = signatureBuffer.slice(0, -1);
    }

    if (parsedScript.scriptType === 'taprootScriptPathSpend') {
      if (!prevOutputs) {
        throw new Error(`prevOutputs not set`);
      }
      const { controlBlock, pubScript } = parsedScript;
      const leafHash = taproot.getTapleafHash(eccLib, controlBlock, pubScript);
      const signatureHash = transaction.hashForWitnessV1(
        inputIndex,
        prevOutputs.map(({ script }) => script),
        prevOutputs.map(({ value }) => value),
        hashType,
        leafHash
      );

      const signedBy = publicKeys.filter(
        (k) => Buffer.isBuffer(signatureBuffer) && eccLib.verifySchnorr(signatureHash, k, signatureBuffer)
      );

      if (signedBy.length === 0) {
        return { signedBy: undefined, signature: undefined };
      }
      if (signedBy.length === 1) {
        return { signedBy: signedBy[0], signature: signatureBuffer };
      }
      throw new Error(`illegal state: signed by multiple public keys`);
    } else if (parsedScript.scriptType === 'taprootKeyPathSpend') {
      if (!prevOutputs) {
        throw new Error(`prevOutputs not set`);
      }
      const signatureHash = transaction.hashForWitnessV1(
        inputIndex,
        prevOutputs.map(({ script }) => script),
        prevOutputs.map(({ value }) => value),
        hashType
      );
      const result = eccLib.verifySchnorr(signatureHash, publicKeys[0], signatureBuffer);
      return result
        ? { signedBy: publicKeys[0], signature: signatureBuffer }
        : { signedBy: undefined, signature: undefined };
    } else {
      // slice the last byte from the signature hash input because it's the hash type
      const { signature, hashType } = ScriptSignature.decode(signatureBuffer);
      const transactionHash =
        parsedScript.scriptType === 'p2shP2wsh' || parsedScript.scriptType === 'p2wsh'
          ? transaction.hashForWitnessV0(inputIndex, parsedScript.pubScript, amount, hashType)
          : transaction.hashForSignatureByNetwork(inputIndex, parsedScript.pubScript, amount, hashType);
      const signedBy = publicKeys.filter((publicKey) =>
        eccLib.verify(
          transactionHash,
          publicKey,
          signature,
          /*
            Strict verification (require lower-S value), as required by BIP-0146
            https://github.com/bitcoin/bips/blob/master/bip-0146.mediawiki
            https://github.com/bitcoin-core/secp256k1/blob/ac83be33/include/secp256k1.h#L478-L508
            https://github.com/bitcoinjs/tiny-secp256k1/blob/v1.1.6/js.js#L231-L233
          */
          true
        )
      );

      if (signedBy.length === 0) {
        return { signedBy: undefined, signature: undefined };
      }
      if (signedBy.length === 1) {
        return { signedBy: signedBy[0], signature: signatureBuffer };
      }
      throw new Error(`illegal state: signed by multiple public keys`);
    }
  });
}

/**
 * @deprecated use {@see verifySignatureWithPublicKeys} instead
 * @param transaction
 * @param inputIndex
 * @param amount
 * @param verificationSettings - if publicKey is specified, returns true iff any signature is signed by publicKey.
 * @param prevOutputs - must be set for p2tr transactions
 */
export function verifySignature<TNumber extends number | bigint>(
  transaction: UtxoTransaction<TNumber>,
  inputIndex: number,
  amount: TNumber,
  verificationSettings: VerificationSettings = {},
  prevOutputs?: TxOutput<TNumber>[]
): boolean {
  const signatureVerifications = getSignatureVerifications(
    transaction,
    inputIndex,
    amount,
    verificationSettings,
    prevOutputs
  ).filter(
    (v) =>
      // If no publicKey is set in verificationSettings, all signatures must be valid.
      // Otherwise, a single valid signature by the specified pubkey is sufficient.
      verificationSettings.publicKey === undefined ||
      (v.signedBy !== undefined &&
        (verificationSettings.publicKey.equals(v.signedBy) ||
          verificationSettings.publicKey.slice(1).equals(v.signedBy)))
  );

  return signatureVerifications.length > 0 && signatureVerifications.every((v) => v.signedBy !== undefined);
}

/**
 * @param v
 * @param publicKey
 * @return true iff signature is by publicKey (or xonly variant of publicKey)
 */
function isSignatureByPublicKey(v: SignatureVerification, publicKey: Buffer): boolean {
  return (
    !!v.signedBy &&
    (v.signedBy.equals(publicKey) ||
      /* for p2tr signatures, we pass the pubkey in 33-byte format recover it from the signature in 32-byte format */
      (publicKey.length === 33 && isSignatureByPublicKey(v, publicKey.slice(1))))
  );
}

/**
 * @param transaction
 * @param inputIndex
 * @param prevOutputs
 * @param publicKeys
 * @return array with signature corresponding to n-th key, undefined if no match found
 */
export function getSignaturesWithPublicKeys<TNumber extends number | bigint>(
  transaction: UtxoTransaction<TNumber>,
  inputIndex: number,
  prevOutputs: TxOutput<TNumber>[],
  publicKeys: Buffer[]
): Array<Buffer | undefined> {
  if (transaction.ins.length !== prevOutputs.length) {
    throw new Error(`input length must match prevOutputs length`);
  }

  const signatureVerifications = getSignatureVerifications(
    transaction,
    inputIndex,
    prevOutputs[inputIndex].value,
    {},
    prevOutputs
  );

  return publicKeys.map((publicKey) => {
    const v = signatureVerifications.find((v) => isSignatureByPublicKey(v, publicKey));
    return v ? v.signature : undefined;
  });
}

/**
 * @param transaction
 * @param inputIndex
 * @param prevOutputs - transaction outputs for inputs
 * @param publicKeys - public keys to check signatures for
 * @return array of booleans indicating a valid signature for every pubkey in _publicKeys_
 */
export function verifySignatureWithPublicKeys<TNumber extends number | bigint>(
  transaction: UtxoTransaction<TNumber>,
  inputIndex: number,
  prevOutputs: TxOutput<TNumber>[],
  publicKeys: Buffer[]
): boolean[] {
  return getSignaturesWithPublicKeys(transaction, inputIndex, prevOutputs, publicKeys).map((s) => s !== undefined);
}

/**
 * Wrapper for {@see verifySignatureWithPublicKeys} for single pubkey
 * @param transaction
 * @param inputIndex
 * @param prevOutputs
 * @param publicKey
 * @return true iff signature is valid
 */
export function verifySignatureWithPublicKey<TNumber extends number | bigint>(
  transaction: UtxoTransaction<TNumber>,
  inputIndex: number,
  prevOutputs: TxOutput<TNumber>[],
  publicKey: Buffer
): boolean {
  return verifySignatureWithPublicKeys(transaction, inputIndex, prevOutputs, [publicKey])[0];
}

export function getDefaultSigHash(network: Network, scriptType?: ScriptType): number {
  switch (getMainnet(network)) {
    case networks.bitcoincash:
    case networks.bitcoinsv:
    case networks.bitcoingold:
    case networks.ecash:
      return Transaction.SIGHASH_ALL | UtxoTransaction.SIGHASH_FORKID;
    default:
      switch (scriptType) {
        case 'p2tr':
        case 'p2trMusig2':
          return Transaction.SIGHASH_DEFAULT;
        default:
          return Transaction.SIGHASH_ALL;
      }
  }
}

export function signInputP2shP2pk<TNumber extends number | bigint>(
  txBuilder: UtxoTransactionBuilder<TNumber>,
  vin: number,
  keyPair: BIP32Interface
): void {
  const prevOutScriptType = 'p2sh-p2pk';
  const { redeemScript, witnessScript } = createOutputScriptP2shP2pk(keyPair.publicKey);
  keyPair.network = txBuilder.network;

  txBuilder.sign({
    vin,
    prevOutScriptType,
    keyPair,
    hashType: getDefaultSigHash(txBuilder.network as Network),
    redeemScript,
    witnessScript,
    witnessValue: undefined,
  });
}

export function signInput2Of3<TNumber extends number | bigint>(
  txBuilder: UtxoTransactionBuilder<TNumber>,
  vin: number,
  scriptType: ScriptType2Of3,
  pubkeys: Triple<Buffer>,
  keyPair: BIP32Interface,
  cosigner: Buffer,
  amount: TNumber
): void {
  let controlBlock;
  let redeemScript;
  let witnessScript;

  const prevOutScriptType = scriptType2Of3AsPrevOutType(scriptType);
  if (scriptType === 'p2tr') {
    ({ witnessScript, controlBlock } = createSpendScriptP2tr(pubkeys, [keyPair.publicKey, cosigner]));
  } else {
    ({ redeemScript, witnessScript } = createOutputScript2of3(pubkeys, scriptType));
  }

  keyPair.network = txBuilder.network;

  txBuilder.sign({
    vin,
    prevOutScriptType,
    keyPair,
    hashType: getDefaultSigHash(txBuilder.network as Network, scriptType),
    redeemScript,
    witnessScript,
    witnessValue: amount,
    controlBlock,
  });
}

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


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