1
0

coinUtils.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. /* Import core modules. */
  2. const _ = require('lodash')
  3. const bch = require('bitcore-lib-cash')
  4. const debug = require('debug')('shuffle:utils')
  5. /* Import local modules. */
  6. const getCoinDetails = require('./_utils/getCoinDetails')
  7. const getKeypairFromWif = require('./_utils/getKeypairFromWif')
  8. const prepareShuffleInsAndOuts = require('./_utils/prepareShuffleInsAndOuts')
  9. /**
  10. * Check Sufficient Funds
  11. */
  12. const checkSufficientFunds = function (inputs, amount) {
  13. debug('Funds verification (amount):', amount)
  14. // Currently this is done in the `getCoinDetails` function
  15. // and it's called just before we add the player to the round.
  16. // It's also done as we're building the shuffle transaction.
  17. // I want to break this functionality out so we can check from
  18. // any time.
  19. }
  20. /**
  21. * Verify Transaction Signature
  22. */
  23. const verifyTransactionSignature = function (
  24. shuffleTxInstance, inputSigData, publicKeyHexOfSigner
  25. ) {
  26. // debug('Verify transaction signature',
  27. // 'shuffleTxInstance', shuffleTxInstance,
  28. // 'inputSigData', inputSigData,
  29. // 'publicKeyHexOfSigner', publicKeyHexOfSigner
  30. // )
  31. /* Set input to sign. */
  32. const inputToSign = _.reduce(
  33. shuffleTxInstance.inputs, function (keeper, oneInput, arrayIndex) {
  34. // If we already found the right input, pass it through
  35. // without bothering to check the others;
  36. if (keeper) {
  37. return keeper
  38. }
  39. const asJson = oneInput.toObject()
  40. if (inputSigData.prevTxId === asJson.prevTxId && Number(inputSigData.vout) === Number(asJson.outputIndex)) {
  41. return {
  42. input: oneInput,
  43. inputIndex: arrayIndex
  44. }
  45. } else {
  46. return undefined
  47. }
  48. }, undefined)
  49. // debug('Input to sign:', inputToSign)
  50. /* Validate input to sign. */
  51. if (!inputToSign) {
  52. return false
  53. }
  54. /* Set signer public key. */
  55. const signerPublicKey = bch.PublicKey(publicKeyHexOfSigner)
  56. /* Set signature instance. */
  57. const signatureInstance = bch.crypto.Signature
  58. .fromTxFormat(Buffer.from(inputSigData.signature, 'hex'))
  59. // debug('Signature instance:', signatureInstance)
  60. /* Set signature object. */
  61. const signatureObject = {
  62. signature: signatureInstance,
  63. publicKey: signerPublicKey,
  64. inputIndex: inputToSign.inputIndex,
  65. sigtype: signatureInstance.nhashtype
  66. }
  67. // debug('Signature object:', signatureObject)
  68. /* Initialize verification results. */
  69. let verificationResults = false
  70. try {
  71. verificationResults = inputToSign.input
  72. .isValidSignature(shuffleTxInstance, signatureObject)
  73. } catch (nope) {
  74. console.error(nope) // eslint-disable-line no-console
  75. verificationResults = false
  76. }
  77. // debug('Verification results:', verificationResults)
  78. /* Validate verification results. */
  79. if (verificationResults) {
  80. return {
  81. success: true,
  82. inputIndex: signatureObject.inputIndex,
  83. signature: signatureObject
  84. }
  85. } else {
  86. return {
  87. success: false
  88. }
  89. }
  90. }
  91. /**
  92. * Get Shuffle Transaction and Signature
  93. *
  94. * Builds the partially signed transaction that will eventually be broadcast
  95. * to the the network.
  96. *
  97. * It returns a serialized (as JSON ) version of the transaction before any
  98. * signatures are added as well as the fully formed transaction with only our
  99. * signature applied.
  100. */
  101. const getShuffleTxAndSignature = function (options) {
  102. // debug('Get Shuffle Tx and Signature (options):', options)
  103. /* Set inputs. */
  104. const inputs = options.inputs
  105. /* Set outputs. */
  106. const outputs = options.outputs
  107. /* Set shuffle transaction. */
  108. const shuffleTransaction = new bch.Transaction()
  109. /* Initialize my input. */
  110. let myInput
  111. /* Loop through ALL inputs. */
  112. for (let oneInput of inputs) {
  113. /* Set player public key. */
  114. const playerPubKey = bch.PublicKey(oneInput.player.coin.publicKey)
  115. /* Set transaction input. */
  116. const txInput = new bch.Transaction.UnspentOutput({
  117. txid: oneInput.txid,
  118. outputIndex: oneInput.vout,
  119. address: playerPubKey.toAddress(),
  120. scriptPubKey: bch.Script.fromAddress(playerPubKey.toAddress()),
  121. satoshis: oneInput.satoshis
  122. })
  123. debug('Transaction input:', txInput)
  124. /* Set shuffle transaction. */
  125. shuffleTransaction.from(txInput)
  126. // WARNING: For some stupid reason, bitcoincashjs's `PublicKeyHashInput`
  127. // instances are showing the outputIndex field which should be
  128. // type number or string as type 'undefined'.
  129. // Idfk, just be aware.
  130. const grabIt = _.find(shuffleTransaction.inputs, function (txInput) {
  131. /* Set buffer string. */
  132. const bufferString = txInput.prevTxId.toString('hex')
  133. return oneInput.txid === bufferString && Number(oneInput.vout) === Number(txInput.outputIndex)
  134. })
  135. /* Fix the sequence number. */
  136. Object.assign(grabIt, { sequenceNumber: 0xfffffffe })
  137. /* Add public key to input's script. */
  138. grabIt.setScript(bch.Script('21' + oneInput.player.coin.publicKey))
  139. /* Validate our input. */
  140. if (oneInput.player.isMe) {
  141. // debug('My (oneInput):', oneInput)
  142. myInput = oneInput
  143. }
  144. }
  145. /* Loop through ALL outputs. */
  146. for (let oneOutput of outputs) {
  147. // debug('Shuffle transaction (oneOutput)',
  148. // oneOutput,
  149. // 'legacyAddress', oneOutput.legacyAddress,
  150. // 'cashAddress', oneOutput.cashAddress,
  151. // 'satoshis', oneOutput.satoshis
  152. // )
  153. shuffleTransaction.to(oneOutput.cashAddress, oneOutput.satoshis)
  154. }
  155. /* Set version 1. */
  156. shuffleTransaction.setVersion(1)
  157. /* Set pre-signed transaction. */
  158. const preSignedTx = shuffleTransaction.toObject()
  159. debug('Get shuffle transaction and signature (preSignedTx):', preSignedTx)
  160. /* Sign transaction. */
  161. shuffleTransaction.sign(
  162. new bch.PrivateKey.fromWIF(myInput.player.coin.wif))
  163. /* Set signature instance. */
  164. const sigInstance = shuffleTransaction
  165. .getSignatures(myInput.player.coin.wif)[0]
  166. // debug('Signature instance:', sigInstance)
  167. /* Return transaction / signature package. */
  168. return {
  169. serialized: preSignedTx,
  170. tx: shuffleTransaction,
  171. signature: sigInstance.signature.toTxFormat().toString('hex')
  172. }
  173. }
  174. /**
  175. * Build Shuffle Transaction
  176. *
  177. * NOTE: FOR DEVELOPMENT PURPOSE ONLY
  178. * ----------------------------
  179. * An intermediary function, used to switch between the different
  180. * transaction building methods currently being evaluated.
  181. */
  182. const buildShuffleTransaction = async function (options) {
  183. // debug('Build shuffle transaction (options):', options)
  184. /* Initialize ins and outs. */
  185. let insAndOuts
  186. try {
  187. insAndOuts = await this.prepareShuffleInsAndOuts({
  188. players: options.players,
  189. feeSatoshis: options.feeSatoshis
  190. })
  191. } catch (nope) {
  192. console.error('cannot prepare inputs and outputs for shuffle Transaction') // eslint-disable-line no-console
  193. throw nope
  194. }
  195. debug('Build shuffle transaction (insAndOuts):', insAndOuts)
  196. /* Set shuffle transaction data. */
  197. const shuffleTxData = await this.getShuffleTxAndSignature({
  198. inputs: insAndOuts.inputs,
  199. outputs: insAndOuts.outputs
  200. })
  201. debug('Build shuffle transaction (shuffleTxData):', shuffleTxData)
  202. /* Return the results. */
  203. return {
  204. tx: shuffleTxData.tx,
  205. inputs: insAndOuts.inputs,
  206. outputs: insAndOuts.outputs,
  207. serialized: shuffleTxData.serialized,
  208. signatureBase64: Buffer
  209. .from(shuffleTxData.signature, 'utf-8')
  210. .toString('base64')
  211. }
  212. }
  213. /* Export module. */
  214. module.exports = {
  215. getKeypairFromWif,
  216. checkSufficientFunds,
  217. getCoinDetails,
  218. verifyTransactionSignature,
  219. prepareShuffleInsAndOuts,
  220. getShuffleTxAndSignature,
  221. buildShuffleTransaction
  222. }