Source: inputs.js

  1. /**
  2. * This module provides functions for sorting & validating multisig
  3. * transaction inputs.
  4. *
  5. * @module inputs
  6. */
  7. import {validateHex} from './utils';
  8. import {multisigBraidDetails} from './multisig';
  9. /**
  10. * Represents a transaction input.
  11. *
  12. * The [`Multisig`]{@link module:multisig.MULTISIG} object represents
  13. * the address the corresponding UTXO belongs to.
  14. *
  15. * @typedef MultisigTransactionInput
  16. * @type {Object}
  17. * @property {string} txid - The transaction ID where funds were received
  18. * @property {number} index - The index in the transaction referred to by {txid}
  19. * @property {module:multisig.Multisig} multisig - The multisig object encumbering this UTXO
  20. *
  21. */
  22. /**
  23. * 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.
  24. *
  25. * @param {module:inputs.MultisigTransactionInput[]} inputs - inputs to sort
  26. * @returns {module:inputs.MultisigTransactionInput[]} inputs sorted according to BIP69
  27. */
  28. export function sortInputs(inputs) {
  29. return inputs.sort((input1, input2) => {
  30. if (input1.txid > input2.txid) {
  31. return 1;
  32. } else {
  33. if (input1.txid < input2.txid) {
  34. return -1;
  35. } else {
  36. return ((input1.index < input2.index) ? -1 : 1);
  37. }
  38. }
  39. });
  40. }
  41. /**
  42. * Validates the given transaction inputs.
  43. *
  44. * Returns an error message if there are no inputs. Passes each output to [`validateMultisigInput`]{@link module:transactions.validateOutput}.
  45. *
  46. * If the function is called with braidRequired, an additional check is performed
  47. * on each input's multisig to make sure that it is braid-aware. In other words,
  48. * does the input know the set of ExtendedPublicKeys it came from?
  49. *
  50. * @param {module:inputs.MultisigTransactionInput[]} inputs - inputs to validate
  51. * @param {boolean} [braidRequired] - inputs need to have braid details attached to them
  52. * @returns {string} empty if valid or corresponding validation message if not
  53. */
  54. export function validateMultisigInputs(inputs, braidRequired) {
  55. if (!inputs || inputs.length === 0) { return "At least one input is required."; }
  56. let utxoIDs = [];
  57. for (let inputIndex = 0; inputIndex < inputs.length; inputIndex++) {
  58. const input = inputs[inputIndex];
  59. if (braidRequired && input.multisig && !multisigBraidDetails(input.multisig)) {
  60. return `At least one input cannot be traced back to its set of extended public keys.`;
  61. }
  62. const error = validateMultisigInput(input);
  63. if (error) { return error; }
  64. const utxoID = `${input.txid}:${input.index}`;
  65. if (utxoIDs.includes(utxoID)) {
  66. return `Duplicate input: ${utxoID}`;
  67. }
  68. utxoIDs.push(utxoID);
  69. }
  70. return "";
  71. }
  72. /**
  73. * Validates the given transaction input.
  74. *
  75. * - Validates the presence and value of the transaction ID (`txid`) property.
  76. *
  77. * - Validates the presence and value of the transaction index (`index`) property.
  78. *
  79. * - Validates the presence of the `multisig` property.
  80. *
  81. * @param {module:inputs.MultisigTransactionInput} input - input to validate
  82. * @returns {string} empty if valid or corresponding validation message if not
  83. *
  84. */
  85. export function validateMultisigInput(input) {
  86. if (!input.txid) {
  87. return `Does not have a transaction ID ('txid') property.`;
  88. }
  89. let error = validateTransactionID(input.txid);
  90. if (error) { return error; }
  91. if (input.index !== 0 && (!input.index)) {
  92. return `Does not have a transaction index ('index') property.`;
  93. }
  94. error = validateTransactionIndex(input.index);
  95. if (error) { return error; }
  96. if (!input.multisig) {
  97. return `Does not have a multisig object ('multisig') property.`;
  98. }
  99. return "";
  100. }
  101. const TXID_LENGTH = 64;
  102. /**
  103. * Validates the given transaction ID.
  104. *
  105. * @param {string} txid - transaction ID to validate
  106. * @returns {string} empty if valid or corresponding validation message if not
  107. *
  108. */
  109. export function validateTransactionID(txid) {
  110. if (txid === null || txid === undefined || txid === '') {
  111. return "TXID cannot be blank.";
  112. }
  113. let error = validateHex(txid);
  114. if (error) {
  115. return `TXID is invalid (${error})`;
  116. }
  117. if (txid.length !== TXID_LENGTH) {
  118. return `TXID is invalid (must be ${TXID_LENGTH}-characters)`;
  119. }
  120. return '';
  121. }
  122. /**
  123. * Validates the given transaction index.
  124. *
  125. * @param {string|number} indexString - transaction index to validate
  126. * @returns {string} empty if valid or corresponding validation message if not
  127. *
  128. */
  129. export function validateTransactionIndex(indexString) {
  130. if (indexString === null || indexString === undefined || indexString === '') {
  131. return "Index cannot be blank.";
  132. }
  133. const index = parseInt(indexString, 10);
  134. if (!isFinite(index)) {
  135. return "Index is invalid";
  136. }
  137. if (index < 0) {
  138. return "Index cannot be negative.";
  139. }
  140. return "";
  141. }