Source: inputs.js

/**
 * This module provides functions for sorting & validating multisig
 * transaction inputs.
 *
 * @module inputs
 */

import {validateHex} from './utils';
import {multisigBraidDetails} from './multisig';

 /**
 * Represents a transaction input.
 *
 * The [`Multisig`]{@link module:multisig.MULTISIG} object represents
 * the address the corresponding UTXO belongs to.
 *
 * @typedef MultisigTransactionInput
 * @type {Object}
 * @property {string} txid - The transaction ID where funds were received
 * @property {number} index - The index in the transaction referred to by {txid}
 * @property {module:multisig.Multisig} multisig - The multisig object encumbering this UTXO
 *
 */

/**
 * Sorts the given inputs according to the [BIP69 standard]{@link https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki#transaction-inputs}: ascending lexicographic order.
 *
 * @param {module:inputs.MultisigTransactionInput[]} inputs - inputs to sort
 * @returns {module:inputs.MultisigTransactionInput[]} inputs sorted according to BIP69
 */
export function sortInputs(inputs) {
  return inputs.sort((input1, input2) => {
    if (input1.txid > input2.txid) {
      return 1;
    } else {
      if (input1.txid < input2.txid) {
        return -1;
      } else {
        return ((input1.index < input2.index) ? -1 : 1);
      }
    }
  });
}

/**
 * Validates the given transaction inputs.
 *
 * Returns an error message if there are no inputs.  Passes each output to [`validateMultisigInput`]{@link module:transactions.validateOutput}.
 *
 * If the function is called with braidRequired, an additional check is performed
 * on each input's multisig to make sure that it is braid-aware. In other words,
 * does the input know the set of ExtendedPublicKeys it came from?
 *
 * @param {module:inputs.MultisigTransactionInput[]} inputs - inputs to validate
 * @param {boolean} [braidRequired] - inputs need to have braid details attached to them
 * @returns {string} empty if valid or corresponding validation message if not
 */
export function validateMultisigInputs(inputs, braidRequired) {
  if (!inputs || inputs.length === 0) { return "At least one input is required."; }
  let utxoIDs = [];
  for (let inputIndex = 0; inputIndex < inputs.length; inputIndex++) {
    const input = inputs[inputIndex];
    if (braidRequired && input.multisig && !multisigBraidDetails(input.multisig)) {
        return `At least one input cannot be traced back to its set of extended public keys.`;
    }
    const error = validateMultisigInput(input);
    if (error) { return error; }
    const utxoID = `${input.txid}:${input.index}`;
    if (utxoIDs.includes(utxoID)) {
      return `Duplicate input: ${utxoID}`;
    }
    utxoIDs.push(utxoID);
  }
  return "";
}

/**
 * Validates the given transaction input.
 *
 * - Validates the presence and value of the transaction ID (`txid`) property.
 *
 * - Validates the presence and value of the transaction index (`index`) property.
 *
 * - Validates the presence of the `multisig` property.
 *
 * @param {module:inputs.MultisigTransactionInput} input - input to validate
 * @returns {string} empty if valid or corresponding validation message if not
 *
 */
export function validateMultisigInput(input) {
  if (!input.txid) {
    return `Does not have a transaction ID ('txid') property.`;
  }
  let error = validateTransactionID(input.txid);
  if (error) { return error; }
  if (input.index !== 0 && (!input.index)) {
    return `Does not have a transaction index ('index') property.`;
  }
  error = validateTransactionIndex(input.index);
  if (error) { return error; }
  if (!input.multisig) {
    return `Does not have a multisig object ('multisig') property.`;
  }
  return "";
}

const TXID_LENGTH = 64;

/**
 * Validates the given transaction ID.
 *
 * @param {string} txid - transaction ID to validate
 * @returns {string} empty if valid or corresponding validation message if not
 *
 */
export function validateTransactionID(txid) {
  if (txid === null || txid === undefined || txid === '') {
    return "TXID cannot be blank.";
  }
  let error = validateHex(txid);
  if (error) {
    return `TXID is invalid (${error})`;
  }
  if (txid.length !== TXID_LENGTH) {
    return `TXID is invalid (must be ${TXID_LENGTH}-characters)`;
  }
  return '';
}

/**
 * Validates the given transaction index.
 *
 * @param {string|number} indexString - transaction index to validate
 * @returns {string} empty if valid or corresponding validation message if not
 *
 */
export function validateTransactionIndex(indexString) {
  if (indexString === null || indexString === undefined || indexString === '') {
    return "Index cannot be blank.";
  }
  const index = parseInt(indexString, 10);
  if (!isFinite(index)) {
    return "Index is invalid";
  }
  if (index < 0) {
    return "Index cannot be negative.";
  }
  return "";
}