Source: signatures.js

  1. /**
  2. * This module provides functions for validating and handling
  3. * multisig transaction signatures.
  4. *
  5. * @module signatures
  6. */
  7. import BigNumber from 'bignumber.js';
  8. import bip66 from "bip66";
  9. import {ECPair, Transaction} from "bitcoinjs-lib";
  10. import {P2SH_P2WSH} from "./p2sh_p2wsh";
  11. import {P2WSH} from "./p2wsh";
  12. import {
  13. multisigAddressType,
  14. multisigRedeemScript,
  15. multisigWitnessScript,
  16. multisigPublicKeys,
  17. multisigTotalSigners,
  18. } from "./multisig";
  19. import {
  20. unsignedMultisigTransaction,
  21. } from "./transactions";
  22. /**
  23. * Validate a multisig signature for given input and public key.
  24. *
  25. * @param {module:networks.NETWORKS} network - bitcoin network
  26. * @param {module:inputs.MultisigTransactionInput[]} inputs - multisig transaction inputs
  27. * @param {module:outputs.TransactionOutput[]} outputs - transaction outputs
  28. * @param {number} inputIndex - the index where the input appears in the transaction
  29. * @param {string} inputSignature - signature to validate
  30. * @returns {string|boolean} false if invalid or corresponding public key
  31. * @example
  32. * import {
  33. * generateMultisigFromPublicKeys, TESTNET, P2SH,
  34. * unsignedMultisigTransaction,
  35. * validateMultisigSignature,
  36. * } from "unchained-bitcoin";
  37. * const pubkey1 = "03a...";
  38. * const pubkey2 = "03b...";
  39. * const multisig = generateMultisigFromPublicKeys(TESTNET, P2SH, 2, pubkey1, pubkey2);
  40. * const inputs = [
  41. * {
  42. * txid: "ae...",
  43. * index: 0,
  44. * multisig,
  45. * },
  46. * // other inputs...
  47. * ];
  48. * const outputs = [
  49. * {
  50. * address: "2N...",
  51. * amountSats: 90000,
  52. * },
  53. * // other outputs...
  54. * ];
  55. * const unsignedTransaction = unsignedMultisigTransaction(TESTNET, inputs, outputs);
  56. * // Use unsignedTransaction to obtain a signature.
  57. * const transactionSignature = ["304...", // other input signatures...];
  58. * // Validate signature for input 0
  59. * const result = validateMultisigSignature(TESTNET, inputs, outputs, 0, transactionSignature[0]);
  60. * switch (result) {
  61. * case false:
  62. * // signature was invalid
  63. * case pubkey1:
  64. * // signature was valid for pubkey1
  65. * case pubkey2:
  66. * // signature was valid for pubkey2
  67. * default:
  68. * // ...
  69. * }
  70. */
  71. export function validateMultisigSignature(network, inputs, outputs, inputIndex, inputSignature) {
  72. const hash = multisigSignatureHash(network, inputs, outputs, inputIndex);
  73. const signatureBuffer = multisigSignatureBuffer(signatureNoSighashType(inputSignature));
  74. const input = inputs[inputIndex];
  75. const publicKeys = multisigPublicKeys(input.multisig);
  76. for (let publicKeyIndex=0; publicKeyIndex < multisigTotalSigners(input.multisig); publicKeyIndex++) {
  77. const publicKey = publicKeys[publicKeyIndex];
  78. const publicKeyBuffer = Buffer.from(publicKey, 'hex');
  79. const keyPair = ECPair.fromPublicKey(publicKeyBuffer);
  80. if (keyPair.verify(hash, signatureBuffer)) {
  81. return publicKey;
  82. }
  83. }
  84. return false;
  85. }
  86. /**
  87. * This function takes a DER encoded signature and returns it without the SIGHASH_BYTE
  88. * @param {string} signature inputSignature which includes DER encoding bytes and may include SIGHASH byte
  89. * @return {string} signature_no_sighash with sighash_byte removed
  90. */
  91. export function signatureNoSighashType(signature) {
  92. const len = parseInt(signature.slice(2,4), 16);
  93. if (len === (signature.length - 4) / 2) return signature;
  94. else return signature.slice(0, -2);
  95. }
  96. /**
  97. * Returns the multisig Signature Hash for an input at inputIndex
  98. * @param {module:networks.NETWORKS} network - bitcoin network
  99. * @param {module:inputs.MultisigTransactionInput[]} inputs - multisig transaction inputs
  100. * @param {module:outputs.TransactionOutput[]} outputs - transaction outputs
  101. * @param {number} inputIndex - the index where the input appears in the transaction
  102. * @return {Buffer} unsignedTransaction hash in a Buffer for consumption by ECPair.verify
  103. */
  104. function multisigSignatureHash(network, inputs, outputs, inputIndex) {
  105. const unsignedTransaction = unsignedMultisigTransaction(network, inputs, outputs);
  106. const input = inputs[inputIndex];
  107. if (multisigAddressType(input.multisig) === P2WSH || multisigAddressType(input.multisig) === P2SH_P2WSH) {
  108. return unsignedTransaction.hashForWitnessV0(inputIndex, multisigWitnessScript(input.multisig).output, BigNumber(input.amountSats).toNumber(), Transaction.SIGHASH_ALL);
  109. } else {
  110. return unsignedTransaction.hashForSignature(inputIndex, multisigRedeemScript(input.multisig).output, Transaction.SIGHASH_ALL);
  111. }
  112. }
  113. /**
  114. * Create a signature buffer that can be passed to ECPair.verify
  115. * @param {string} signature - a DER encoded signature string
  116. * @return {Buffer} signatureBuffer - correctly allocated buffer with relevant r, S information from the encoded signature
  117. */
  118. function multisigSignatureBuffer(signature) {
  119. const encodedSignerInputSignatureBuffer = Buffer.from(signature, 'hex');
  120. const decodedSignerInputSignatureBuffer = bip66.decode(encodedSignerInputSignatureBuffer);
  121. const {r, s} = decodedSignerInputSignatureBuffer;
  122. // The value returned from the decodedSignerInputSignatureBuffer has
  123. // a few edge cases that need to be handled properly. There exists a mismatch between the
  124. // DER serialization and the ECDSA requirements, namely:
  125. // DER says that its highest bit states polarity (positive/negative)
  126. // ECDSA says no negatives, only positives.
  127. // So in the case where DER would result in a negative, a one-byte 0x00 is added to the value
  128. // NOTE: this can happen on r and on S.
  129. // See https://transactionfee.info/charts/bitcoin-script-ecdsa-length/ for more information
  130. // Truncate the leading 0x00 if r or S is 33 bytes long
  131. let rToUse = r.byteLength > 32 ? r.slice(1) : r;
  132. // Technically, this could be done but extremely unlikely in the current era.
  133. // let sToUse = s.byteLength > 32 ? s.slice(1) : s;
  134. const signatureBuffer = Buffer.alloc(64);
  135. // r/s bytelength could be < 32, in which case, zero padding needed
  136. signatureBuffer.set(Buffer.from(rToUse), 32 - rToUse.byteLength);
  137. signatureBuffer.set(Buffer.from(s), 64 - s.byteLength);
  138. return signatureBuffer;
  139. }