1
0

ShuffleRound.js 54 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477
  1. /* Import core modules. */
  2. const _ = require('lodash')
  3. const bch = require('bitcore-lib-cash')
  4. const debug = require('debug')('shuffle:round')
  5. const EventEmitter = require('events').EventEmitter
  6. const Nito = require('nitojs')
  7. /* Import local modules. */
  8. const cryptoUtils = require('./cryptoUtils.js')
  9. const coinUtils = require('./coinUtils.js')
  10. /* Import CommChannel (Class). */
  11. const CommChannel = require('./CommChannel.js')
  12. /**
  13. * Shuffle
  14. *
  15. * The de-facto unbiased shuffle algorithm is the Fisher-Yates
  16. * (aka Knuth) Shuffle. (see: https://github.com/coolaj86/knuth-shuffle)
  17. */
  18. const _shuffle = function (array) {
  19. var currentIndex = array.length, temporaryValue, randomIndex
  20. // While there remain elements to shuffle...
  21. while (0 !== currentIndex) {
  22. // Pick a remaining element...
  23. randomIndex = Math.floor(Math.random() * currentIndex)
  24. currentIndex -= 1
  25. // And swap it with the current element.
  26. temporaryValue = array[currentIndex]
  27. array[currentIndex] = array[randomIndex]
  28. array[randomIndex] = temporaryValue
  29. }
  30. return array
  31. }
  32. /* Initialize magic number. */
  33. // const magic = Buffer.from('42bcc32669467873', 'hex')
  34. /**
  35. * Delay (Execution)
  36. */
  37. const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms))
  38. /**
  39. * Shuffle Round (Class)
  40. */
  41. class ShuffleRound extends EventEmitter {
  42. constructor (clientOptions) {
  43. super()
  44. /* Initialize client options. */
  45. for (let oneOption in clientOptions) {
  46. this[oneOption] = clientOptions[oneOption]
  47. }
  48. /* Initialize done flag. */
  49. this.done = false
  50. /* Initialize phase. */
  51. this.phase = ''
  52. /* Initialize util. */
  53. // TODO: Rename to `utils`.
  54. this.util = {
  55. /* Tools for encryption and message sign / verify. */
  56. crypto: cryptoUtils,
  57. /* Tools that make REST calls for blockchain data. */
  58. coin: coinUtils
  59. }
  60. /* Initialize ephemeral keypair. */
  61. // NOTE: A public and private keypair, destroyed at the end of a shuffle
  62. // round. Its only purpose is to sign and verify protocol
  63. // messages to ensure the participants aren't being
  64. // cheated / attacked by the server or each other.
  65. this.ephemeralKeypair = this.util.crypto.generateKeypair()
  66. /* Initialize encryption keypair. */
  67. // NOTE: A public and private keypair, destroyed at the end of a shuffle
  68. // round. It's used to encrypt and decrypt message fields during
  69. // the shuffle round so they are kept private from the server and
  70. // the other players in the round.
  71. this.encryptionKeypair = this.util.crypto.generateKeypair()
  72. /* Initialize hooks. */
  73. this.hooks = this.hooks || {}
  74. /* Validate shuffled hook. */
  75. if (!_.isFunction(this.hooks.shuffled)) {
  76. console.error(`A valid shuffle address generation hook was not provided!`) // eslint-disable-line no-console
  77. throw new Error('BAD_SHUFFLE_FN')
  78. }
  79. /* Initialize shuffled. */
  80. this.shuffled = this.hooks.shuffled()
  81. /* Validate change hook. */
  82. // NOTE: Make sure either a change generation function or change
  83. // keypair object was provided. Use the keypair, if we got both.
  84. if (!_.isFunction(this.hooks.change)) {
  85. console.error(`A valid change generation hook was not provided!`) // eslint-disable-line no-console
  86. throw new Error('BAD_CHANGE_FN')
  87. }
  88. /* Initialize change. */
  89. this.change = this.hooks.change()
  90. /* Initialize (shuffle) players. */
  91. // NOTE: This is where we keep our representation of all the shufflers
  92. // in the round (including us).
  93. this.players = []
  94. /* Initialize output addresses. */
  95. // NOTE: Once we reach the "shuffle" phase, this array will house the
  96. // addresses that each player's shuffled coins will be sent to.
  97. this.outputAddresses = []
  98. /* Initialize shuffle transaction. */
  99. // NOTE: Used to store the partially signed transaction after it
  100. // is generated but before its broadcasted to the network.
  101. this.shuffleTx = {
  102. isBuilding: false,
  103. // NOTE: We will add each signature and input data to this
  104. // collection as it's received during the verification
  105. // and submission phase.
  106. signatures: []
  107. }
  108. /* Initialize round completion flag. */
  109. this.roundComplete = false
  110. /* Initialize success flag. */
  111. this.success = false
  112. /* Initialize round error. */
  113. // NOTE: This object will be extended with error data in the event
  114. // that the round ends unexpectedly for any reason. This
  115. // includes a protocol error on behalf of any player in the
  116. // round (ourselves included) as well as if an exception is
  117. // thrown in this library.
  118. this.roundError = {
  119. // shortCode: 'BAD_SIG',
  120. // errorObject: [ Error instance containing a stacktrace ],
  121. // isProtocolError: true,
  122. // isException: false,
  123. // accusedPlayer: [ Object containing player data ]
  124. }
  125. /* Initialize communications channel. */
  126. this.comms = new CommChannel({
  127. serverUri: this.serverUri
  128. }, this)
  129. /* Handle server message. */
  130. this.comms.on('serverMessage', async (someServerMessage) => {
  131. try {
  132. await this.actOnMessage(someServerMessage)
  133. } catch (nope) {
  134. console.error('Failed to act right in response to server message:', nope) // eslint-disable-line no-console
  135. this.writeDebugFile()
  136. }
  137. })
  138. /* Handle protocol violation. */
  139. this.comms.on('protocolViolation', this.assignBlame.bind(this))
  140. /* Handle connection error. */
  141. this.comms.on('connectionError', this.handleCommsError.bind(this))
  142. /* Handle disconnection. */
  143. this.comms.on('disconnected', (commsDisconnectMessage) => {
  144. /* Validate round completion. */
  145. if (this.roundComplete) {
  146. debug('The shuffle Round has completed!')
  147. } else {
  148. /* eslint-disable-next-line no-console */
  149. console.error('Our connection to the CashShuffle server is REKT!')
  150. /* Set success flag. */
  151. this.success = false
  152. /* Set round copmletion flag. */
  153. this.roundComplete = true
  154. /* Update round error. */
  155. Object.assign(this.roundError, {
  156. shortCode: 'COMMS_DISCONNECT',
  157. errorObject: new Error(commsDisconnectMessage),
  158. isProtocolError: false,
  159. isException: false
  160. })
  161. /* End shuffle round. */
  162. this.endShuffleRound()
  163. }
  164. })
  165. /* Handle connection. */
  166. // this.comms.on('connected', (socket) => {
  167. // debug('socket', socket)
  168. this.comms.on('connected', () => {
  169. /* Set round phase. */
  170. this.phase = 'registration'
  171. this.emit('phase', 'registration')
  172. try {
  173. // console.log(
  174. // '\nSENDING REGISTRATION MESSAGE',
  175. // this.protocolVersion,
  176. // this.poolAmount,
  177. // this.ephemeralKeypair.publicKey
  178. // )
  179. this.comms
  180. .sendMessage(
  181. 'registration',
  182. this.protocolVersion,
  183. this.poolAmount,
  184. this.ephemeralKeypair.publicKey
  185. )
  186. } catch (nope) {
  187. console.error('Couldnt send registration message:', nope.message) // eslint-disable-line no-console
  188. }
  189. })
  190. this.ready()
  191. .catch((nope) => {
  192. console.error('ERROR:', nope) // eslint-disable-line no-console
  193. })
  194. .then(() => {
  195. //
  196. })
  197. return this
  198. }
  199. /**
  200. * Handle Communications Error
  201. */
  202. handleCommsError (someError) {
  203. /* eslint-disable-next-line no-console */
  204. console.error('Something has gone wrong with our communication channel:', someError.message)
  205. /* Update round error. */
  206. this.roundError = {
  207. shortCode: 'COMS_ERR',
  208. errorObject: someError,
  209. isProtocolError: false,
  210. isException: true
  211. }
  212. /* End shuffle round. */
  213. this.endShuffleRound()
  214. }
  215. /**
  216. * Ready
  217. */
  218. async ready () {
  219. this.emit('debug', { message: 'beginning-round' })
  220. /* Setup server connection. */
  221. try {
  222. await this.comms.connect()
  223. } catch (nope) {
  224. console.error('Failure!', nope) // eslint-disable-line no-console
  225. throw nope
  226. }
  227. }
  228. /**
  229. * Act On Message
  230. *
  231. * Process incoming websocket events which contain the prototype buffer
  232. * encoded server messages.
  233. */
  234. async actOnMessage (jsonMessage) {
  235. if (typeof window !== 'undefined') {
  236. /* eslint-disable-next-line no-console */
  237. console.log('Act on message (jsonMessage):', jsonMessage.pruned.message)
  238. } else {
  239. debug('Act on message (jsonMessage):', jsonMessage.pruned.message)
  240. }
  241. this.emit('notice', jsonMessage.pruned.message)
  242. /* Set message type. */
  243. const messageType =
  244. jsonMessage.pruned.message && jsonMessage.pruned.messageType
  245. /* Validate message type. */
  246. if (!messageType) {
  247. throw new Error('BAD_MESSAGE_PARSING')
  248. }
  249. /* Set message. */
  250. const message = jsonMessage.pruned.message
  251. /* Initialize new phase name. */
  252. let newPhaseName
  253. // debug('Attempting to act on', messageType, 'message\n\n');
  254. /* Handle message type. */
  255. switch (messageType) {
  256. /**
  257. * The server has informed us of the number of players currently in the
  258. * pool. This fires every time a player joins or leaves.
  259. *
  260. * NOTE: We always get one along without server greeting.
  261. */
  262. case 'playerCount':
  263. /* Set number of players. */
  264. this.numberOfPlayers = Number(message['number'])
  265. break
  266. /**
  267. * The server has accepted our pool registration message and replied
  268. * with our player number and a session id to identify us within this
  269. * pool and round.
  270. */
  271. case 'serverGreeting':
  272. /* Set our player number. */
  273. this.myPlayerNumber = Number(message['number'])
  274. /* Set our session id. */
  275. this.session = message['session']
  276. break
  277. /**
  278. * This is a message sent to all players to inform them that it's now
  279. * time to share their change address as well as their second ephemeral
  280. * public key (later used to decrypt the encrypted output addresses).
  281. */
  282. case 'announcementPhase':
  283. /* Set new phase name. */
  284. newPhaseName = _.isString(
  285. message['phase']) ? message['phase'].toLowerCase() : undefined
  286. /* Validate new phase name. */
  287. if (newPhaseName && newPhaseName === 'announcement') {
  288. /* Set phase. */
  289. this.phase = 'announcement'
  290. this.emit('phase', 'announcement')
  291. /* Set number of players. */
  292. this.numberOfPlayers = Number(message['number'])
  293. try {
  294. this.broadcastTransactionInput()
  295. } catch (nope) {
  296. console.error('Error broadcasting broadcastTransactionInput:', nope) // eslint-disable-line no-console
  297. }
  298. } else {
  299. console.error('Problem with server phase message') // eslint-disable-line no-console
  300. if (_.get(jsonMessage, 'packets[0].packet.fromKey.key')) {
  301. this.assignBlame({
  302. reason: 'INVALIDFORMAT',
  303. accused: _.get(jsonMessage, 'packets[0].packet.fromKey.key')
  304. })
  305. }
  306. }
  307. break
  308. case 'incomingVerificationKeys':
  309. try {
  310. await this.addPlayerToRound(message)
  311. } catch (nope) {
  312. console.error('Error broadcasting broadcastTransactionInput:', nope) // eslint-disable-line no-console
  313. }
  314. // If we've received the message from all players (including us)
  315. // containing their `verificationKey` and the coin they wish to
  316. // shuffle, send the next protocol message if we are player one.
  317. if (this.myPlayerNumber === _.get(_.minBy(this.players, 'playerNumber'), 'playerNumber')) {
  318. try {
  319. await this.announceChangeAddress()
  320. } catch (nope) {
  321. console.error('Error broadcasting changeAddress:', nope) // eslint-disable-line no-console
  322. this.endShuffleRound()
  323. }
  324. }
  325. break
  326. case 'incomingChangeAddress':
  327. /* Validate change address announcement. */
  328. // NOTE: If we are player one, we will have already sent
  329. // this message.
  330. if (!this.comms.outbox.sent['changeAddressAnnounce']) {
  331. await this.announceChangeAddress()
  332. }
  333. debug('Act on message (incomingChangeAddress):',
  334. 'Encryption pubkey', message['message']['key']['key'],
  335. 'Legacy address', message['message']['address']['address']
  336. )
  337. console.log('Act on message (incomingChangeAddress):',
  338. 'Encryption pubkey', message['message']['key']['key'],
  339. 'Legacy address', message['message']['address']['address']
  340. )
  341. /* Update this player with their change address. */
  342. Object.assign(
  343. this.players[_.findIndex(this.players, { session: message['session'] })], {
  344. encryptionPubKey: message['message']['key']['key'],
  345. change: {
  346. legacyAddress: message['message']['address']['address']
  347. }
  348. }
  349. )
  350. /**
  351. * If we are player 1, go ahead and send the first encrypted
  352. * unicast message containing the Bitcoin address that will
  353. * house our shuffled output. This function will return without
  354. * doing anything unless all players.
  355. */
  356. if (_.get(_.minBy(this.players, 'playerNumber'), 'playerNumber') === this.myPlayerNumber) {
  357. this.phase = 'shuffle'
  358. this.emit('phase', 'shuffle')
  359. try {
  360. await this.forwardEncryptedShuffleTxOutputs(undefined, undefined)
  361. } catch (nope) {
  362. console.error('Error broadcasting changeAddress:', nope) // eslint-disable-line no-console
  363. this.endShuffleRound()
  364. }
  365. }
  366. break
  367. case 'incomingEncryptedOutputs': {
  368. newPhaseName = _.isString(message['phase']) ? message['phase'].toLowerCase() : undefined
  369. // Grab the sender of this message by using the verificationKey used
  370. // to sign this protobuff message. The signature has already been
  371. // verified successfully but we're not sure yet if the sender is lying
  372. // about their player number. This check will be performed in the the
  373. // `forwardEncryptedShuffleTxOutputs` function.
  374. const sentBy = _.find(this.players, {
  375. verificationKey: _.get(jsonMessage, 'packets[0].packet.fromKey.key')
  376. })
  377. if (this.phase === 'announcement' && newPhaseName === 'shuffle') {
  378. this.phase = 'shuffle'
  379. this.emit('phase', 'shuffle')
  380. this.forwardEncryptedShuffleTxOutputs(jsonMessage.packets, sentBy)
  381. }
  382. break
  383. }
  384. case 'finalTransactionOutputs':
  385. newPhaseName = _.isString(
  386. message['phase']) ? message['phase'].toLowerCase() : undefined
  387. /* Set new phase name. */
  388. this.phase = newPhaseName
  389. this.emit('phase', newPhaseName)
  390. this.checkFinalOutputsAndDoEquivCheck(jsonMessage.packets)
  391. break
  392. case 'incomingEquivCheck':
  393. try {
  394. await this.processEquivCheckMessage(message)
  395. } catch (nope) {
  396. console.error('Error processing incoming equivCheck:', nope) // eslint-disable-line no-console
  397. }
  398. break
  399. case 'blame':
  400. this.handleBlameMessage(message)
  401. break
  402. case 'incomingInputAndSig':
  403. try {
  404. await this.verifyAndSubmit(message)
  405. } catch (nope) {
  406. console.error('Error processing incoming output and signature:', nope) // eslint-disable-line no-console
  407. }
  408. break
  409. // case '':
  410. // break;
  411. default:
  412. break
  413. }
  414. // debug('Finished acting on', messageType, 'message\n\n');
  415. }
  416. /**
  417. * Process Websockets Error
  418. */
  419. processWsError (someError) {
  420. console.error('Oh goodness, something is amiss!', someError) // eslint-disable-line no-console
  421. }
  422. /**
  423. * Write Debug File
  424. */
  425. writeDebugFile () {
  426. this.comms.writeDebugFile(true)
  427. }
  428. /***************************************************************************
  429. BEGIN COINSHUFFLE PROTOCOL METHODS
  430. ----------------------------------
  431. **************************************************************************/
  432. /**
  433. * Broadcast Transaction Input
  434. *
  435. * This function reveals the coin our client wishes to shuffle as well as
  436. * our verificationKey. Although we revealed our verificationKey in our
  437. * server registration message, that message isn't relayed to our peers.
  438. * This is the first message where our peers see the vk.
  439. */
  440. broadcastTransactionInput () {
  441. if (this.comms.outbox.sent['broadcastTransactionInput']) {
  442. return
  443. }
  444. // debug('Revealing our verificationKey and coin to our peers!');
  445. /* Initialize inputs. */
  446. const inputsObject = {}
  447. /* Set inputs. */
  448. inputsObject[this.coin.publicKey.toString('hex')] =
  449. [this.coin.txid + ':' + this.coin.vout]
  450. try {
  451. this.comms
  452. .sendMessage(
  453. 'broadcastTransactionInput',
  454. inputsObject,
  455. this.session,
  456. this.myPlayerNumber,
  457. this.ephemeralKeypair.publicKey
  458. )
  459. } catch (nope) {
  460. console.error('Couldnt send broadcastTransactionInput message:', nope.message) // eslint-disable-line no-console
  461. return this.endShuffleRound()
  462. }
  463. }
  464. /**
  465. * Add Player to Round
  466. *
  467. * This function is called in response to us receiving a new message from
  468. * either ourselves or another player that announces which coin they will
  469. * be shuffling. We should receive one of these messages for each player
  470. * in the round (including ourselves). The messages are unicast
  471. * (no toKey field).
  472. *
  473. * In this function we do ALL of the following:
  474. * 1. Check that the coin exists on the blockchain.
  475. * 2. Check that the coin value is appropriate for the current round.
  476. * 3. Add the player to our internal state data.
  477. *
  478. * NOTE: It's also here where we record each player's verificationKey,
  479. * that the `CommChannel` class uses to verify the signature on all
  480. * future messages.
  481. */
  482. async addPlayerToRound (message) {
  483. /* Initialize player coin. */
  484. const playerCoin = {
  485. publicKey: _.keys(message['message']['inputs'])[0]
  486. }
  487. /* Initialize UTXO info. */
  488. const utxoInfo = _.values(message['message']['inputs'])[0]['coins'][0].split(':')
  489. /* Set player transaction id. */
  490. playerCoin.txid = utxoInfo[0]
  491. /* Set player coin output (index). */
  492. playerCoin.vout = Number(utxoInfo[1])
  493. /* Set player to add. */
  494. const playerToAdd = {
  495. session: message['session'],
  496. playerNumber: Number(message['number']),
  497. isMe: message['session'] === this.session,
  498. verificationKey: message['fromKey']['key'],
  499. coin: playerCoin
  500. }
  501. /* Validate player. */
  502. if (playerToAdd.isMe) {
  503. Object.assign(playerToAdd.coin, this.coin)
  504. }
  505. /* Add player. */
  506. this.players.push(playerToAdd)
  507. // debug('Added player', playerToAdd);
  508. /* Initialize coin details. */
  509. // NOTE: We've already added the player to our pool but we
  510. // still need to verify the data they sent us.
  511. const coinDetails = await this.util.coin
  512. .getCoinDetails(playerCoin.txid, playerCoin.vout)
  513. .catch(err => {
  514. /* eslint-disable-next-line no-console */
  515. console.error('Cannot get coin details', err)
  516. /* Assign blame. */
  517. this.assignBlame({
  518. reason: 'INSUFFICIENTFUNDS',
  519. accused: playerToAdd.verificationKey
  520. })
  521. })
  522. /* Validate coin details. */
  523. // NOTE: Check that the coin is there and big enough
  524. // before adding the player.
  525. if (!coinDetails.satoshis || this.shuffleFee + this.poolAmount > coinDetails.satoshis) {
  526. debug('Insufficient funds for player (coinDetails):', coinDetails)
  527. /* Assign blame. */
  528. this.assignBlame({
  529. reason: 'INSUFFICIENTFUNDS',
  530. accused: playerToAdd.verificationKey
  531. })
  532. return
  533. }
  534. /* Grab player. */
  535. const grabPlayer = _.find(this.players, { session: playerToAdd.session })
  536. /* Validate player. */
  537. // NOTE: If it's our message, add our coin object to
  538. // the player and only update the fiscal properties.
  539. if (playerToAdd.isMe) {
  540. Object.assign(grabPlayer.coin, {
  541. amount: coinDetails.amount,
  542. satoshis: coinDetails.satoshis,
  543. // confirmations: coinDetails.confirmations,
  544. spent: coinDetails.spent
  545. })
  546. } else {
  547. Object.assign(grabPlayer.coin, coinDetails)
  548. }
  549. // debug(`Player ${grabPlayer.playerNumber} updated`);
  550. }
  551. /**
  552. * Announce Change Address
  553. *
  554. * Here we announce our change address, as well as the public key that
  555. * other players should use to encrypt messages meant for our eyes only.
  556. * Primarily, they will use it when encrypting the transaction output
  557. * addresses so we can decrypt them, add our own, and re-encrypt all of
  558. * them for the next player to do the same.
  559. *
  560. * NOTE: Encrypting these output addresses keeps the server from being
  561. * able to keep a record of which coin belongs to which player.
  562. *
  563. * NOTE: This function fires many times but we should only announce our
  564. * change address once. If we've already done this, just return.
  565. */
  566. announceChangeAddress () {
  567. if (this.comms.outbox.sent['changeAddressAnnounce'] ||
  568. this.players.length < this.numberOfPlayers
  569. ) {
  570. return
  571. }
  572. /* Send message. */
  573. this.comms.sendMessage(
  574. 'changeAddressAnnounce',
  575. this.session,
  576. this.myPlayerNumber,
  577. this.change.legacyAddress,
  578. this.encryptionKeypair.publicKeyHex,
  579. this.phase,
  580. this.ephemeralKeypair.publicKey,
  581. this.ephemeralKeypair.privateKey
  582. )
  583. }
  584. /**
  585. * Forward Encrypted Shuffle Transaction Outputs
  586. *
  587. * Implements processing of messages during the shuffling phase.
  588. *
  589. * It performs the following:
  590. * 1. Accepts the encrypted output addresses sent to us in the first
  591. * message of the "shuffle" phase. There should be as few as zero
  592. * and at most all-but-one address(es) in legacy format. They are
  593. * encrypted to our public `encryptionKey` that we shared along
  594. * with our change address in the previous step.
  595. *
  596. * 2. The remaining behavior in this function varies depending on if
  597. * we are are the last player. In both cases though, we will first
  598. * do all of the following:
  599. * (a) Make sure this message came from the previous player.
  600. * (b) Strip off the top layer of encryption from all strings.
  601. * (c) Encrypt and add our own output address that will soon
  602. * contain our shuffled coin.
  603. * (d) Shuffle the entire set of addresses well. If we are the
  604. * last player, we may choose not to.
  605. * (e) Check that each string is unique. If not, assign blame
  606. * and end the round.
  607. *
  608. * 3. If we are the last player, as determined by the highest `number`
  609. * returned by the server in response to our our registration
  610. * message, then we will signal the end of the shuffle stage by
  611. * broadcasting the complete set of shuffled addresses in decrypted
  612. * form to every player as a regular multicast message.
  613. *
  614. * 4. If we are not the last player, we encrypt and add our own
  615. * addresses. We must add one layer of encryption to our address
  616. * for every subsequent player in the round. For example, if we are
  617. * player 3 in a 10 player round, we must add 7 layers of
  618. * encryption, starting with player #10 then working our way back
  619. * to player #4. Then we send all of them to player #4 as a
  620. * signed multi-packet unicast message. Unicast messages are those
  621. * that include toKey field which the server relays only to them.
  622. *
  623. * NOTE: If we are player 1, this function has been called without any
  624. * parameters so there will be nothing for us to decrypt and our
  625. * message will be the first message of the shuffle phase. Before we
  626. * send it though, we need to make sure everyone has sent us their
  627. * decryption keys. If they haven't, just return without doing
  628. * anything. This function will be called again with each new key
  629. * received.
  630. */
  631. forwardEncryptedShuffleTxOutputs (arrayOfPacketObjects, sender) {
  632. /* Initialize ourselves. */
  633. const me = _.find(this.players, { isMe: true })
  634. /* Initialize ordered players. */
  635. const orderedPlayers = _.orderBy(
  636. this.players, ['playerNumber'], ['asc'])
  637. /* Set first player. */
  638. const firstPlayer = _.minBy(orderedPlayers, 'playerNumber')
  639. /* Set last player. */
  640. const lastPlayer = _.maxBy(orderedPlayers, 'playerNumber')
  641. /* Set next player. */
  642. const nextPlayer = orderedPlayers[_.findIndex(orderedPlayers, { isMe: true }) + 1]
  643. /* Set previous player. */
  644. const previousPlayer = orderedPlayers[_.findIndex(orderedPlayers, { isMe: true }) - 1]
  645. /* Validate encryption keys. */
  646. // NOTE: Check that we have received a decryption key from all players,
  647. // and that they are all unique.
  648. //
  649. // NOTE: Uniqueness isn't a protocol requirement, but it probably
  650. // should be.
  651. if (
  652. _.uniq(
  653. _.compact(
  654. orderedPlayers.map(obj => obj['encryptionPubKey'])
  655. )
  656. ).length !== this.players.length) {
  657. debug('Waiting for the remaining encryption keys:', orderedPlayers)
  658. return
  659. }
  660. /* Initialize string for next player. */
  661. const stringsForNextPlayer = []
  662. /* Validate player data. */
  663. if (me.playerNumber !== firstPlayer.playerNumber) {
  664. // Make sure the player who sent us this message is who it should be
  665. if (sender.playerNumber !== previousPlayer.playerNumber) {
  666. debug(`Player ${sender.playerNumber} is not player ${previousPlayer.playerNumber} despite saying so`)
  667. this.assignBlame({
  668. reason: 'LIAR',
  669. accused: sender.verificationKey
  670. })
  671. return
  672. }
  673. /* Retrieve decrypted string. */
  674. const decryptedStrings = _.reduce(arrayOfPacketObjects, (results, onePacket) => {
  675. try {
  676. /* Set decryption results. */
  677. const decryptionResults = this.util.crypto
  678. .decrypt(
  679. _.get(onePacket, 'packet.message.str'),
  680. this.encryptionKeypair.privateKeyHex
  681. )
  682. debug('Forward encrypted shuffle tx outputs:',
  683. 'onePacket', onePacket,
  684. 'privateKeyHex', this.encryptionKeypair.privateKeyHex,
  685. decryptionResults
  686. )
  687. /* Add decryption to results. */
  688. results.strings.push(decryptionResults.toString('utf-8'))
  689. } catch (nope) {
  690. console.error('Cannot decrypt') // eslint-disable-line no-console
  691. results.errors.push({
  692. packet: onePacket,
  693. error: nope
  694. })
  695. }
  696. return results
  697. }, {
  698. strings: [],
  699. errors: []
  700. })
  701. debug('Forward encrypted shuffle tx outputs (decryptedStrings):', decryptedStrings)
  702. /* Validate decrypted string. */
  703. // NOTE: Blame our sender if the ciphertext cannot be decrypted.
  704. // It may or may not be their fault, but someone has to be
  705. // the fall guy.
  706. if (decryptedStrings.errors.length) {
  707. this.assignBlame({
  708. reason: 'INVALIDFORMAT',
  709. accused: sender.verificationKey
  710. })
  711. }
  712. _.each(
  713. decryptedStrings.strings, (oneThing) => {
  714. stringsForNextPlayer.push(oneThing)
  715. }
  716. )
  717. }
  718. /* Set our encrypted output address. */
  719. // NOTE: Add our output address after first encrypting it with the
  720. // public keys of all subsequent players in the round except.
  721. const ourEncryptedOutputAddress = _.reduceRight(
  722. orderedPlayers, (
  723. encryptedAddressInfo, onePlayer
  724. ) => {
  725. if (nextPlayer && onePlayer.playerNumber >= nextPlayer.playerNumber) {
  726. try {
  727. encryptedAddressInfo.string = this.util.crypto
  728. .encrypt(
  729. encryptedAddressInfo.string,
  730. onePlayer.encryptionPubKey
  731. )
  732. debug(
  733. 'Forward encrypted shuffle tx outputs (encryptedAddressInfo):',
  734. encryptedAddressInfo
  735. )
  736. } catch (nope) {
  737. /* eslint-disable-next-line no-console */
  738. console.error(`Cannot encrypt address for encryptionPubKey ${onePlayer.encryptionPubKey} because ${nope.message}`)
  739. encryptedAddressInfo.errors.push({
  740. player: onePlayer,
  741. error: nope
  742. })
  743. }
  744. }
  745. return encryptedAddressInfo
  746. }, {
  747. errors: [],
  748. string: this.shuffled.legacyAddress
  749. })
  750. /* Validate our encrypted output address. */
  751. if (ourEncryptedOutputAddress.errors.length) {
  752. this.assignBlame({
  753. reason: 'INVALIDFORMAT',
  754. accused: sender.verificationKey
  755. })
  756. }
  757. /* Add our encrypted output address to string for next player. */
  758. stringsForNextPlayer.push(ourEncryptedOutputAddress.string)
  759. /* Do a uniqueness check on the output addresses / ciphertexts. */
  760. if (_.compact(_.uniq(stringsForNextPlayer)).length !== stringsForNextPlayer.length) {
  761. this.assignBlame({
  762. reason: 'MISSINGOUTPUT',
  763. accused: sender.verificationKey
  764. })
  765. }
  766. /**
  767. * Shuffle Array
  768. */
  769. const shuffleArray = function (someArray, num) {
  770. return (
  771. num > 0 ?
  772. shuffleArray(_shuffle(someArray), num - 1) :
  773. _shuffle(someArray)
  774. )
  775. }
  776. /* Validate if we are the last player. */
  777. if (me.playerNumber === lastPlayer.playerNumber) {
  778. debug(`Broadcasting final shuffled output addresses ${stringsForNextPlayer}!`)
  779. /* Send message. */
  780. this.comms.sendMessage(
  781. 'broadcastFinalOutputAddresses',
  782. this.session,
  783. me.playerNumber,
  784. shuffleArray(stringsForNextPlayer, 100),
  785. 'broadcast',
  786. this.ephemeralKeypair.publicKey,
  787. this.ephemeralKeypair.privateKey
  788. )
  789. } else {
  790. debug('Sending encrypted outputs:',
  791. stringsForNextPlayer,
  792. 'to player',
  793. nextPlayer.playerNumber,
  794. '(', nextPlayer.verificationKey, ')'
  795. )
  796. /* Send message. */
  797. this.comms.sendMessage(
  798. 'forwardEncryptedOutputs',
  799. this.session,
  800. me.playerNumber,
  801. shuffleArray(stringsForNextPlayer, 100),
  802. this.phase,
  803. nextPlayer.verificationKey,
  804. this.ephemeralKeypair.publicKey,
  805. this.ephemeralKeypair.privateKey
  806. )
  807. }
  808. }
  809. /**
  810. * Check Final Outputs and Do Equivocation Check
  811. *
  812. * This function performs processing of the "broadcast" phase message sent
  813. * by the final player in the round. This message announces the final
  814. * set of shuffled output addresses.
  815. *
  816. * This function does all of the following:
  817. *
  818. * 1. Ensure our address is in list of output addresses.
  819. * If not, blame and exit.
  820. * 2. Broadcast our own "equivocation check" message.
  821. * 3. Compute hash of outputs string and broadcast it.
  822. *
  823. * TODO: Check that the message was actually sent by the last player
  824. * in the round.
  825. */
  826. checkFinalOutputsAndDoEquivCheck (signedPackets) {
  827. const me = _.find(this.players, { isMe: true })
  828. const finalOutputAddresses = signedPackets.map(obj => obj['packet']['message']['str'])
  829. debug(
  830. 'Check final outputs and do equiv check (finalOutputAddresses):',
  831. finalOutputAddresses
  832. )
  833. /* Make sure our address was included. If not, blame! */
  834. if (finalOutputAddresses.indexOf(this.shuffled.legacyAddress) < 0) {
  835. debug(`Our address isn't in the final outputs!`)
  836. this.assignBlame({
  837. reason: 'MISSINGOUTPUT',
  838. // accused: _.get(messageObject, _.get(signedPackets[0], 'packet.fromKey.key'))
  839. accused: _.get(signedPackets, _.get(signedPackets[0], 'packet.fromKey.key'))
  840. })
  841. }
  842. // Attach the entire array of ordered output addresses to our
  843. // players. Although we don't know which address belongs to which
  844. // player ( they've been shuffled by everyone ), the order becomes
  845. // important later because it effects the transaction output order
  846. // which has implications for it's signature.
  847. for (let n = this.players.length; n >= 0; n--) {
  848. debug('this.players[ n ]:', n, this.players[ n ])
  849. if (typeof this.players[ n ] !== 'undefined') {
  850. Object.assign(this.players[ n ], { finalOutputAddresses })
  851. }
  852. }
  853. /* Set equivocation hash (plaintext). */
  854. this.equivHashPlaintext = '[\'' +
  855. finalOutputAddresses.join('\', \'') +
  856. '\'][\'' +
  857. _.orderBy(this.players, 'playerNumber').map(obj => obj['encryptionPubKey']
  858. ).join('\', \'') +
  859. '\']'
  860. /* Calculate equivocation hash. */
  861. this.equivHash = bch.crypto.Hash
  862. .sha256sha256(Buffer.from(this.equivHashPlaintext, 'utf-8'))
  863. .toString('base64')
  864. /* Advance to the next phase. */
  865. this.phase = 'EQUIVOCATION_CHECK'
  866. this.emit('phase', 'EQUIVOCATION_CHECK')
  867. /* Now broadcast the results of our "equivocation check". */
  868. this.comms.sendMessage(
  869. 'broadcastEquivCheck',
  870. this.session,
  871. me.playerNumber,
  872. this.equivHash,
  873. this.phase,
  874. this.ephemeralKeypair.publicKey,
  875. this.ephemeralKeypair.privateKey
  876. )
  877. }
  878. /**
  879. * Process Equivocation Check Message
  880. *
  881. * This function implements processing of messages on Equivocation Check
  882. * phase(phase # 4).
  883. *
  884. * It does the following:
  885. * 1. Verify if hashes from all players are the same. If it's not,
  886. * goes to the blame phase.
  887. *
  888. * 2. If hashes are the same it sets the next phase as verification
  889. * and submission phase.
  890. */
  891. async processEquivCheckMessage (prunedMessage) {
  892. const me = _.find(this.players, { isMe: true })
  893. /* Set first player. */
  894. const firstPlayer = _.minBy(this.players, 'playerNumber')
  895. /* Set last player. */
  896. // const lastPlayer = _.maxBy(this.players, 'playerNumber')
  897. /* Add the hash provided by the player to that player's state data. */
  898. const sender = Object.assign(
  899. this.players[_.findIndex(this.players, { session: prunedMessage['session'] })], {
  900. equivCheck: _.get(prunedMessage, 'message.hash.hash')
  901. }
  902. )
  903. debug(
  904. 'Got a processEquivCheck message from', sender.verificationKey,
  905. 'with hash', sender.equivCheck
  906. )
  907. const allHashes = _.compact(this.players.map(obj => obj['equivCheck']))
  908. if (allHashes.length === this.players.length) {
  909. // Are all the hashes the same and do they equal ours?
  910. if (_.uniq(allHashes).length === 1 && _.uniq(allHashes)[0] === this.equivHash) {
  911. debug('Everyone passes the EQUIVOCATION_CHECK!')
  912. this.phase = 'VERIFICATION_AND_SUBMISSION'
  913. this.emit('phase', 'VERIFICATION_AND_SUBMISSION')
  914. if (me.playerNumber === firstPlayer.playerNumber) {
  915. try {
  916. await this.verifyAndSubmit()
  917. } catch (nope) {
  918. console.error('Error processing incoming output and signature:', nope) // eslint-disable-line no-console
  919. }
  920. }
  921. } else {
  922. debug('Someone failed the equivCheck!')
  923. // for (let onePlayer of round.players) {
  924. for (let onePlayer of this.players) {
  925. if (onePlayer.equivCheck !== me.equivCheck) {
  926. this.assignBlame({
  927. reason: 'EQUIVOCATIONFAILURE',
  928. accused: sender.verificationKey,
  929. hash: onePlayer.equivCheck
  930. }, true)
  931. }
  932. }
  933. }
  934. } else {
  935. // debug('Waiting for more equivCheck messages');
  936. }
  937. }
  938. /**
  939. * Verify and Submit
  940. *
  941. * This function handles messages for the final phase of the protocol
  942. * (phase # 5).
  943. *
  944. * It does the following:
  945. * 1. Creates an unsigned transaction that adheres to the
  946. * CashShuffle spec (input order and amounts, etc).
  947. *
  948. * 2. Partially sign the transaction. Sign our input then broadcast
  949. * its signature to the other players.
  950. *
  951. * 3. Check if we've received the input signature all the other players.
  952. *
  953. * 4. Verify the input signature's of all players. If there is a wrong
  954. * signature, go to the blame phase.
  955. *
  956. * 5. If everything is good, use the signatures to finish signing
  957. * the transaction.
  958. *
  959. * 6. Broadcast the transaction to the network.
  960. *
  961. * 7. Set the done flag and cleanup the round.
  962. */
  963. async verifyAndSubmit (prunedMessage) {
  964. debug('Verify and submit (prunedMessage):', prunedMessage)
  965. /* Initialize ordered players. */
  966. const orderedPlayers = _.orderBy(this.players, ['playerNumber'], ['asc'])
  967. /* Initialize first player. */
  968. const firstPlayer = _.minBy(orderedPlayers, 'playerNumber')
  969. /* Initialize last player. */
  970. // const lastPlayer = _.maxBy(orderedPlayers, 'playerNumber')
  971. /* Initialize ourselves. */
  972. const me = _.find(this.players, { isMe: true })
  973. // If we got a signature message before we've finished building
  974. // the partially signed transaction, wait up to 15 seconds or until
  975. // the transaction is done building before letting processing
  976. // this message. Otherwise, chaos reigns.
  977. if (this.shuffleTx.isBuilding) {
  978. /* Set wait until time. */
  979. const waitUntilThisTime = new Date().getTime() + (1000 * 15)
  980. while (this.shuffleTx.isBuilding) {
  981. /* Set time now. */
  982. const timeNow = new Date().getTime()
  983. if (timeNow > waitUntilThisTime || !this.shuffleTx.isBuilding) {
  984. this.shuffleTx.isBuilding = false
  985. } else {
  986. await delay(500)
  987. }
  988. }
  989. }
  990. // If we haven't built the shuffle transaction and
  991. // broadcast our signature, do so now.
  992. if (!this.comms.outbox.sent['broadcastSignatureAndUtxo']) {
  993. // Set the isBuilding flag so incoming messages don't trigger
  994. // multiple transaction build attempts and multiple signature
  995. // broadcasts. It sometimes takes a few seconds to build the
  996. // partially signed transactions because we also hit a REST
  997. // endpoint to validate user's have sufficient funds.
  998. this.shuffleTx.isBuilding = true
  999. let shuffleTransaction
  1000. try {
  1001. shuffleTransaction = await this.util.coin.buildShuffleTransaction({
  1002. players: this.players,
  1003. feeSatoshis: this.shuffleFee
  1004. })
  1005. } catch (nope) {
  1006. console.error('Problem building shuffle transaction:', nope) // eslint-disable-line no-console
  1007. this.writeDebugFile()
  1008. }
  1009. Object.assign(this.shuffleTx, {
  1010. serialized: shuffleTransaction.serialized,
  1011. tx: shuffleTransaction.tx,
  1012. inputs: shuffleTransaction.inputs,
  1013. outputs: shuffleTransaction.outputs
  1014. })
  1015. // Broadcast our transaction signature. If the other players
  1016. // are able to apply it to their copy of the transaction then
  1017. // the shuffleRound is complete.
  1018. this.comms.sendMessage(
  1019. 'broadcastSignatureAndUtxo',
  1020. this.session,
  1021. me.playerNumber,
  1022. me.coin.txid + ':' + me.coin.vout,
  1023. shuffleTransaction.signatureBase64,
  1024. this.phase,
  1025. this.ephemeralKeypair.publicKey,
  1026. this.ephemeralKeypair.privateKey
  1027. )
  1028. // Turn off the isBuilding sign so any queued up signature
  1029. // checks may now occur.
  1030. this.shuffleTx.isBuilding = false
  1031. }
  1032. // The CashShuffle protocol dictates that the first player in the
  1033. // round is responsible for first broadcasting their transaction
  1034. // signature. So if we ARE the first player, we call this function
  1035. // without any parameters after we've received and verified the hashes.
  1036. // We will exit now after sending the protocol message and this function
  1037. // will be immediately called again (this time with parameters) as the
  1038. // server sends us our own signature message.
  1039. if (firstPlayer.playerNumber === me.playerNumber && !prunedMessage) {
  1040. return
  1041. }
  1042. /* Set unspent transaction output. */
  1043. const utxo = _.get(prunedMessage, 'message.signatures[0].utxo')
  1044. /* Build new signature data. */
  1045. const newSigData = {
  1046. prevTxId: utxo.split(':')[0],
  1047. vout: Number(utxo.split(':')[1]),
  1048. signature: Buffer.from(
  1049. _.get(
  1050. prunedMessage,
  1051. 'message.signatures[0].signature.signature'
  1052. ), 'base64').toString('utf-8')
  1053. }
  1054. debug('Verify and submit (newSigData):', newSigData)
  1055. // Assert(len(sig) >= 8 and len(sig) <= 72)
  1056. // Assert(sig[0] == 0x30)
  1057. // Assert(sig[1] == len(sig)-2) # Check length
  1058. // Assert(sig[2] == 0x02)
  1059. /* Add new signature data to shuffle signatures. */
  1060. this.shuffleTx.signatures.push(newSigData)
  1061. /* Retrieve signer. */
  1062. const signer = _.find(this.players, (onePlayer) => {
  1063. return onePlayer.coin.txid === newSigData.prevTxId && Number(onePlayer.coin.vout) === newSigData.vout
  1064. })
  1065. debug('Verify and submit (signer):', signer)
  1066. /* Validate signer. */
  1067. if (!signer) {
  1068. this.assignBlame({
  1069. reason: 'INVALIDSIGNATURE',
  1070. accused: _.get(prunedMessage, 'fromKey.key')
  1071. })
  1072. return
  1073. }
  1074. debug(`Got a shuffle transaction signature for coin ${utxo}`)
  1075. // Verify that the signature we've been given is valid for the shuffle
  1076. // transaction input they've stated. If so, we will add that signature
  1077. // to our transaction. If not, we will abort and blame the sender.
  1078. // Note, the function returns the data necessary to add the signature.
  1079. // That data takes the form below.
  1080. //
  1081. // {
  1082. // success: true,
  1083. // inputIndex: signatureObject.inputIndex,
  1084. // signature: signatureObject
  1085. // };
  1086. let sigVerifyResults
  1087. try {
  1088. sigVerifyResults = this.util.coin
  1089. .verifyTransactionSignature(
  1090. this.shuffleTx.tx,
  1091. newSigData,
  1092. _.get(signer, 'coin.publicKey')
  1093. )
  1094. } catch (nope) {
  1095. console.error('Error when trying to validate signature', nope) // eslint-disable-line no-console
  1096. this.assignBlame({
  1097. reason: 'INVALIDSIGNATURE',
  1098. accused: _.get(prunedMessage, 'fromKey.key')
  1099. })
  1100. return
  1101. }
  1102. /* Validate signature (for UTXO). */
  1103. if (sigVerifyResults && sigVerifyResults.success) {
  1104. debug(`Shuffle transaction signature for ${utxo} checks out!`)
  1105. // If it was us that sent the message, we don't need to apply
  1106. // the signature. Our signature was applied during the creation
  1107. // of the shuffle transaction. We only need to apply the other
  1108. // player's signatures.
  1109. if (!signer.isMe) {
  1110. // debug(`Applying signature to input${sigVerifyResults.inputIndex}!`);
  1111. try {
  1112. this.shuffleTx.tx.inputs[sigVerifyResults.inputIndex]
  1113. .addSignature(this.shuffleTx.tx, sigVerifyResults.signature)
  1114. } catch (nope) {
  1115. /* eslint-disable-next-line no-console */
  1116. console.error('We failed to apply a signature to our transaction. Looks like our fault', nope)
  1117. // TODO: throw and cleanup
  1118. }
  1119. }
  1120. } else {
  1121. debug(`Bad signature for coin ${utxo}`)
  1122. this.assignBlame({
  1123. reason: 'INVALIDSIGNATURE',
  1124. accused: _.get(prunedMessage, 'fromKey.key')
  1125. })
  1126. }
  1127. /* Initialize fully signed flag. */
  1128. let txIsFullySigned
  1129. try {
  1130. txIsFullySigned = this.shuffleTx.tx.isFullySigned()
  1131. } catch (nope) {
  1132. console.error('Malformed shuffle transaction', nope) // eslint-disable-line no-console
  1133. this.endShuffleRound()
  1134. }
  1135. if (txIsFullySigned && this.shuffleTx.signatures.length === this.numberOfPlayers) {
  1136. debug(`Broadcasting CashShuffle tx ${this.shuffleTx.tx.hash} to the network!`)
  1137. let submissionResults
  1138. // debug('Broadcasting raw tx:',
  1139. // this.shuffleTx.tx.toBuffer('hex').toString('hex'))
  1140. debug('Broadcasting raw tx:',
  1141. this.shuffleTx.tx.toBuffer('hex').toString('hex'))
  1142. try {
  1143. /* Send raw transaction. */
  1144. submissionResults = await Nito.Transaction
  1145. .sendRawTransaction(this.shuffleTx.tx.toBuffer('hex').toString('hex'))
  1146. this.emit('complete', {
  1147. txid: this.shuffleTx.tx.toBuffer('hex').toString('hex'),
  1148. submissionResults,
  1149. })
  1150. } catch (nope) {
  1151. console.error('Error broadcasting transaction to the network:', nope) // eslint-disable-line no-console
  1152. this.endShuffleRound()
  1153. return
  1154. }
  1155. if (submissionResults) {
  1156. Object.assign(this.shuffleTx, {
  1157. results: submissionResults
  1158. })
  1159. }
  1160. const allOutputAddressesUsed = this.shuffleTx.tx.outputs.map(oneOutput => {
  1161. return oneOutput.script.toAddress().toString()
  1162. })
  1163. // Add a property so the user's wallet logic can
  1164. // quickly tell if this change address can be reused.
  1165. Object.assign(this.change, {
  1166. usedInShuffle: allOutputAddressesUsed
  1167. .indexOf(this.change.legacyAddress) > -1
  1168. })
  1169. this.success = true
  1170. this.endShuffleRound()
  1171. } else {
  1172. debug('Waiting on more signatures...')
  1173. }
  1174. }
  1175. /**
  1176. * End Shuffle Round
  1177. */
  1178. endShuffleRound(writeDebugFileAnyway) {
  1179. debug(`Shuffle has ended with success [ ${ this.success } ]`)
  1180. this.roundComplete = true
  1181. if (!this.success || writeDebugFileAnyway) {
  1182. debug('Writing debug file..')
  1183. this.writeDebugFile()
  1184. }
  1185. // Close this round's connection to the server
  1186. this.comms._wsClient.close()
  1187. const msg = `Coin ${this.coin.txid}:${this.coin.vout} has been successfully shuffled!`
  1188. this.emit('notice', msg)
  1189. this.emit('shuffle')
  1190. }
  1191. /**
  1192. * Stop
  1193. */
  1194. stop() {
  1195. /* Set complete flag. */
  1196. this.roundComplete = true
  1197. // Close this round's connection to the server
  1198. this.comms._wsClient.close()
  1199. debug(`Shuffle has stopped.`)
  1200. }
  1201. /**
  1202. * Handle Blame
  1203. *
  1204. * When we receive a message from another player accusing someone
  1205. * of violating the protocol.
  1206. */
  1207. handleBlameMessage (messageObject) {
  1208. /* Retrieve key of accused. */
  1209. const keyOfAccused = _.get(messageObject, 'message.blame.accused.key')
  1210. /* Set accused. */
  1211. const accused = _.find(this.players, { verificationKey: keyOfAccused })
  1212. /* Validate accused. */
  1213. if (accused.isMe) {
  1214. debug(`I'M THE ONE BEING BLAMED. HOW RUDE!!`)
  1215. console.log(`I'M THE ONE BEING BLAMED. HOW RUDE!!`)
  1216. } else {
  1217. debug('Player', accused.verificationKey, 'is to blame!')
  1218. console.log('Player', accused.verificationKey, 'is to blame!')
  1219. }
  1220. /* Write debug file. */
  1221. this.writeDebugFile()
  1222. }
  1223. /**
  1224. * Assign Blame
  1225. *
  1226. * When we conclude that a player has violated the protocol and we need to
  1227. * send out a blame message.
  1228. *
  1229. * Possible Ban Reasons:
  1230. * INSUFFICIENTFUNDS = 0
  1231. * DOUBLESPEND = 1
  1232. * EQUIVOCATIONFAILURE = 2
  1233. * SHUFFLEFAILURE = 3
  1234. * SHUFFLEANDEQUIVOCATIONFAILURE = 4
  1235. * INVALIDSIGNATURE = 5
  1236. * MISSINGOUTPUT = 6
  1237. * LIAR = 7
  1238. * INVALIDFORMAT = 8
  1239. */
  1240. /*
  1241. {
  1242. reason: < enum string citing reason for blame accusation >,
  1243. accused: < verification key in hex format of player who 's being accused >,
  1244. invalid: < an array of protobuff packets that provide evidence of fault > ,
  1245. hash: < hash provided by accused which differs from our own > ,
  1246. keypair: {
  1247. key: < private key > ,
  1248. public: < public key >
  1249. }
  1250. }
  1251. */
  1252. assignBlame (details, keepAlive) {
  1253. debug(`Issuing a formal blame message against ${details.accused} for ${details.reason}`)
  1254. /* Send message. */
  1255. this.comms.sendMessage(
  1256. 'blameMessage',
  1257. details,
  1258. this.session,
  1259. this.myPlayerNumber,
  1260. this.ephemeralKeypair.publicKey,
  1261. this.ephemeralKeypair.privateKey
  1262. )
  1263. if (!keepAlive) {
  1264. this.endShuffleRound()
  1265. }
  1266. }
  1267. }
  1268. module.exports = ShuffleRound