Source: signatures.js

/**
 * This module provides functions for validating and handling
 * multisig transaction signatures.
 * 
 * @module signatures
 */

import BigNumber from 'bignumber.js';
import bip66 from "bip66";
import {ECPair, Transaction} from "bitcoinjs-lib";

import {P2SH_P2WSH} from "./p2sh_p2wsh";
import {P2WSH} from "./p2wsh";
import {
  multisigAddressType,
  multisigRedeemScript,
  multisigWitnessScript,
  multisigPublicKeys,
  multisigTotalSigners,
} from "./multisig";
import {
  unsignedMultisigTransaction,
} from "./transactions";

/**
 * Validate a multisig signature for given input and public key.
 * 
 * @param {module:networks.NETWORKS} network - bitcoin network
 * @param {module:inputs.MultisigTransactionInput[]} inputs - multisig transaction inputs
 * @param {module:outputs.TransactionOutput[]} outputs - transaction outputs
 * @param {number} inputIndex - the index where the input appears in the transaction
 * @param {string} inputSignature - signature to validate
 * @returns {string|boolean} false if invalid or corresponding public key
 * @example
 * import {
 *   generateMultisigFromPublicKeys, TESTNET, P2SH,
 *   unsignedMultisigTransaction,
 *   validateMultisigSignature,
 * } from "unchained-bitcoin";
 * const pubkey1 = "03a...";
 * const pubkey2 = "03b...";
 * const multisig = generateMultisigFromPublicKeys(TESTNET, P2SH, 2, pubkey1, pubkey2);
 * const inputs = [
 *   {
 *     txid: "ae...",
 *     index: 0,
 *     multisig,
 *   },
 *   // other inputs...
 * ];
 * const outputs = [
 *   {
 *     address: "2N...",
 *     amountSats: 90000,
 *   },
 *   // other outputs...
 * ];
 * const unsignedTransaction = unsignedMultisigTransaction(TESTNET, inputs, outputs);
 * // Use unsignedTransaction to obtain a signature.
 * const transactionSignature = ["304...", // other input signatures...];
 * // Validate signature for input 0
 * const result = validateMultisigSignature(TESTNET, inputs, outputs, 0, transactionSignature[0]);
 * switch (result) {
 *   case false:
 *     // signature was invalid
 *   case pubkey1:
 *     // signature was valid for pubkey1
 *   case pubkey2:
 *     // signature was valid for pubkey2
 *   default:
 *     // ...
 * }
 */
export function validateMultisigSignature(network, inputs, outputs, inputIndex, inputSignature) {
  const hash = multisigSignatureHash(network, inputs, outputs, inputIndex);
  const signatureBuffer = multisigSignatureBuffer(signatureNoSighashType(inputSignature));
  const input = inputs[inputIndex];
  const publicKeys = multisigPublicKeys(input.multisig);
  for (let publicKeyIndex=0; publicKeyIndex < multisigTotalSigners(input.multisig); publicKeyIndex++) {
    const publicKey = publicKeys[publicKeyIndex];
    const publicKeyBuffer = Buffer.from(publicKey, 'hex');
    const keyPair = ECPair.fromPublicKey(publicKeyBuffer);
    if (keyPair.verify(hash, signatureBuffer)) {
      return publicKey;
    }
  }
  return false;
}

/**
 * This function takes a DER encoded signature and returns it without the SIGHASH_BYTE
 * @param {string} signature inputSignature which includes DER encoding bytes and may include SIGHASH byte
 * @return {string} signature_no_sighash with sighash_byte removed
 */
export function signatureNoSighashType(signature) {
  const len = parseInt(signature.slice(2,4), 16);
  if (len === (signature.length - 4) / 2) return signature;
  else return signature.slice(0, -2);
}

/**
 * Returns the multisig Signature Hash for an input at inputIndex
 * @param {module:networks.NETWORKS} network - bitcoin network
 * @param {module:inputs.MultisigTransactionInput[]} inputs - multisig transaction inputs
 * @param {module:outputs.TransactionOutput[]} outputs - transaction outputs
 * @param {number} inputIndex - the index where the input appears in the transaction
 * @return {Buffer} unsignedTransaction hash in a Buffer for consumption by ECPair.verify
 */
function multisigSignatureHash(network, inputs, outputs, inputIndex) {
  const unsignedTransaction = unsignedMultisigTransaction(network, inputs, outputs);
  const input = inputs[inputIndex];
  if (multisigAddressType(input.multisig) === P2WSH || multisigAddressType(input.multisig) === P2SH_P2WSH) {
    return unsignedTransaction.hashForWitnessV0(inputIndex, multisigWitnessScript(input.multisig).output, BigNumber(input.amountSats).toNumber(), Transaction.SIGHASH_ALL);
  } else {
    return unsignedTransaction.hashForSignature(inputIndex, multisigRedeemScript(input.multisig).output, Transaction.SIGHASH_ALL);
  }
}

/**
 * Create a signature buffer that can be passed to ECPair.verify
 * @param {string} signature - a DER encoded signature string
 * @return {Buffer} signatureBuffer - correctly allocated buffer with relevant r, S information from the encoded signature
 */
function multisigSignatureBuffer(signature) {
  const encodedSignerInputSignatureBuffer = Buffer.from(signature, 'hex');
  const decodedSignerInputSignatureBuffer = bip66.decode(encodedSignerInputSignatureBuffer);
  const {r, s} = decodedSignerInputSignatureBuffer;
  // The value returned from the decodedSignerInputSignatureBuffer has
  // a few edge cases that need to be handled properly. There exists a mismatch between the
  // DER serialization and the ECDSA requirements, namely:
  //   DER says that its highest bit states polarity (positive/negative)
  //   ECDSA says no negatives, only positives.
  // So in the case where DER would result in a negative, a one-byte 0x00 is added to the value
  // NOTE: this can happen on r and on S.

  // See https://transactionfee.info/charts/bitcoin-script-ecdsa-length/ for more information

  // Truncate the leading 0x00 if r or S is 33 bytes long
  let rToUse = r.byteLength > 32 ? r.slice(1) : r;
  // Technically, this could be done but extremely unlikely in the current era.
  // let sToUse = s.byteLength > 32 ? s.slice(1) : s;

  const signatureBuffer = Buffer.alloc(64);
  // r/s bytelength could be < 32, in which case, zero padding needed
  signatureBuffer.set(Buffer.from(rToUse), 32 - rToUse.byteLength);
  signatureBuffer.set(Buffer.from(s), 64 - s.byteLength);
  return signatureBuffer;
}