Transaction protocolDefinition()
        throws TimeoutException, Matrix, InterruptedException, FormatException, IOException,
            CoinNetworkException, ExecutionException, AddressFormatException {

      if (amount <= 0) {
        throw new IllegalArgumentException();
      }

      // Phase 1: Announcement
      // In the announcement phase, participants distribute temporary encryption keys.
      phase.set(Phase.Announcement);
      log.info("Player " + me + " begins CoinShuffle protocol " + " with " + N + " players.");
      System.out.println(
          "Player " + me + " begins CoinShuffle protocol " + " with " + N + " players.");

      // Check for sufficient funds.
      // There was a problem with the wording of the original paper which would have meant
      // that player 1's funds never would have been checked, but it's necessary to check
      // everybody.
      blameInsufficientFunds();
      System.out.println("Player " + me + " finds sufficient funds");

      // This will contain the change addresses.
      Map<VerificationKey, Address> changeAddresses = new HashMap<>();

      // Everyone creates a new keypair and sends it around to everyone else.
      // Note that the key for player 1 is not actually used; however, player 1
      // needs to send an announcement message at this point too because he might
      // have a change address. Therefore he just follows the same procedure as
      // everyone else.
      dk = broadcastNewKey(changeAddresses);
      System.out.println("Player " + me + " has broadcasted the new encryption key.");

      // Now we wait to receive similar key from everyone else.
      Map<VerificationKey, Message> announcement;
      try {
        announcement = mailbox.receiveFromMultiple(playerSet(1, N), phase.get());
      } catch (BlameException e) {
        // might receive blame messages about insufficient funds.
        phase.set(Phase.Blame);
        throw fillBlameMatrix();
      }
      System.out.println("Player " + me + " is about to read announcements.");

      readAnnouncements(announcement, encryptionKeys, changeAddresses);

      // Phase 2: Shuffle
      // In the shuffle phase, players go in order and reorder the addresses they have been
      // given by the previous player. They insert their own address in a random location.
      // Everyone has the incentive to insert their own address at a random location, which
      // is sufficient to ensure that the result appears random to everybody.
      phase.set(Phase.Shuffling);
      System.out.println("Player " + me + " reaches phase 2: " + encryptionKeys);

      try {
        // Player one begins the cycle and encrypts its new address with everyone's
        // public encryption key, in order.
        // Each subsequent player reorders the cycle and removes one layer of encryption.
        Message shuffled;
        if (me != 1) {
          shuffled = decryptAll(mailbox.receiveFrom(players.get(me - 1), phase.get()), dk, me - 1);

          if (shuffled == null) {
            blameShuffleMisbehavior();
          }
        } else {
          shuffled = messages.make();
        }

        // Make an encrypted address to the mix, and then shuffle everything ourselves.
        shuffled = shufflePhase(shuffled, addrNew);

        // Pass it along to the next player.
        if (me != N) {
          mailbox.send(shuffled, phase.get(), players.get(me + 1));
        }

        // Phase 3: broadcast outputs.
        // In this phase, the last player just broadcasts the transaction to everyone else.
        phase.set(Phase.BroadcastOutput);
        System.out.println("Player " + me + " reaches phase 3 ");

        newAddresses = readAndBroadcastNewAddresses(shuffled);

      } catch (BlameException e) {
        switch (e.packet.payload().readBlame().reason) {
          case MissingOutput:
            {
              // This was sent by a player in phase 3, which means that the new addresses
              // were sent out by the last player, which means that we need to receive
              // the new addresses before proceeding.
              if (newAddresses == null) {
                newAddresses =
                    readNewAddresses(mailbox.receiveFromBlameless(players.get(N), phase.get()));
              }
              // Continue on to next case.
            }
          case ShuffleFailure:
            {
              blameShuffleMisbehavior();
            }
          default:
            {
              throw fillBlameMatrix();
            }
        }
      }

      // Everyone else receives the broadcast and checks that their message was included.
      if (!newAddresses.contains(addrNew)) {
        phase.set(Phase.Blame);
        mailbox.broadcast(messages.make().attach(Blame.MissingOutput(players.get(N))), phase.get());

        blameShuffleMisbehavior();
      }

      // Phase 4: equivocation check.
      // In this phase, participants check whether any player has history different
      // encryption keys to different players.
      phase.set(Phase.EquivocationCheck);
      System.out.println("Player " + me + " reaches phase 4: ");

      equivocationCheck(encryptionKeys, newAddresses, false);

      // Phase 5: verification and submission.
      // Everyone creates a Bitcoin transaction and signs it, then broadcasts the signature.
      // If all signatures check out, then the transaction is history into the net.
      phase.set(Phase.VerificationAndSubmission);
      System.out.println("Player " + me + " reaches phase 5. ");

      List<VerificationKey> inputs = new LinkedList<>();
      for (int i = 1; i <= N; i++) {
        inputs.add(players.get(i));
      }

      // Generate the join transaction.
      Transaction t = coin.shuffleTransaction(amount, fee, inputs, newAddresses, changeAddresses);

      checkDoubleSpending(t);
      if (t == null) throw new RuntimeException("Transaction in null. This should not happen.");

      // Generate the input script using our signing key.
      Message inputScript = messages.make().attach(t.sign(sk));

      System.out.println("Player " + me + " broadcasts signature ");

      mailbox.broadcast(inputScript, phase.get());

      // Send signature messages around and receive them from other players.
      // During this time we could also get notices of invalid signatures
      // or double spends, so we have to watch out for that.
      Map<VerificationKey, Message> signatureMessages = null;
      boolean invalidClaim = false;
      try {
        signatureMessages = mailbox.receiveFromMultiple(playerSet(1, N), phase.get());
        signatureMessages.put(vk, inputScript);
        System.out.println("Player " + me + " receives signatures ");
      } catch (BlameException e) {
        switch (e.packet.payload().readBlame().reason) {
          case InvalidSignature:
            {
              // Continue receiving messages and ignore any further blame messages.
              signatureMessages =
                  mailbox.receiveFromMultipleBlameless(playerSet(1, N), phase.get());

              invalidClaim = true;
              break;
            }
          case DoubleSpend:
          default:
            {
              phase.set(Phase.Blame);
              // Could receive notice of double spending here.
              throw fillBlameMatrix();
            }
        }
      }

      // Verify the signatures.
      Map<VerificationKey, Bytestring> invalid = new HashMap<>();
      for (Map.Entry<VerificationKey, Message> sig : signatureMessages.entrySet()) {
        VerificationKey key = sig.getKey();
        Bytestring signature = sig.getValue().readSignature();
        signatures.put(key, signature);

        if (!t.addInputScript(signature)) {
          invalid.put(key, signature);
        }
      }

      if (invalid.size() > 0 || invalidClaim) {
        phase.set(Phase.Blame);
        Message blameMessage = messages.make();

        for (Map.Entry<VerificationKey, Bytestring> bad : invalid.entrySet()) {
          VerificationKey key = bad.getKey();
          Bytestring signature = bad.getValue();

          blameMessage = blameMessage.attach(Blame.InvalidSignature(key, signature));
        }
        mailbox.broadcast(blameMessage, phase.get());
        throw fillBlameMatrix();
      }

      // Send the transaction into the net.
      t.send();

      // The protocol has completed successfully.
      phase.set(Phase.Completed);

      return t;
    }