// Check for players with insufficient funds. private void blameInsufficientFunds() throws CoinNetworkException, TimeoutException, Matrix, IOException, InterruptedException, FormatException, AddressFormatException { List<VerificationKey> offenders = new LinkedList<>(); // Check that each participant has the required amounts. for (VerificationKey player : players.values()) { if (!coin.sufficientFunds(player.address(), amount + fee)) { // Enter the blame phase. offenders.add(player); } } // If they do, return. if (offenders.isEmpty()) return; // If not, enter blame phase and find offending transactions. phase.set(Phase.Blame); Message blameMessage = messages.make(); for (VerificationKey offender : offenders) { blameMessage = blameMessage.attach(Blame.InsufficientFunds(offender)); } // Broadcast offending transactions. mailbox.broadcast(blameMessage, phase.get()); // Get all subsequent blame messages. throw fillBlameMatrix(); }
// In the shuffle phase, we have to receive a set of strings from the previous player and // decrypt them all. final Message decryptAll(Message message, DecryptionKey key, int expected) throws IOException, InterruptedException, FormatException { Message decrypted = messages.make(); int count = 0; Set<String> addrs = new HashSet<>(); // Used to check that all addresses are different. while (!message.isEmpty()) { String encrypted = message.readString(); message = message.rest(); addrs.add(encrypted); count++; decrypted = decrypted.attach(key.decrypt(encrypted)); } if (addrs.size() != count || count != expected) { phase.set(Phase.Blame); mailbox.broadcast( messages.make().attach(Blame.ShuffleFailure(players.get(N))), phase.get()); return null; } return decrypted; }
void blameBroadcastShuffleMessages() throws TimeoutException, Matrix, IOException, InterruptedException, FormatException { phase.set(Phase.Blame); log.warn("Player " + me + " enters blame phase and sends broadcast messages."); // Collect all packets from phase 2 and 3. Queue<Packet> evidence = mailbox.getPacketsByPhase(Phase.Shuffling); evidence.addAll(mailbox.getPacketsByPhase(Phase.BroadcastOutput)); // Send them all with the decryption key. mailbox.broadcast( messages.make().attach(Blame.ShuffleAndEquivocationFailure(dk, evidence)), phase.get()); throw fillBlameMatrix(); }
// Some misbehavior that has occurred during the shuffle phase and we want to // find out what happened! private void blameShuffleMisbehavior() throws TimeoutException, Matrix, InterruptedException, FormatException, IOException { // Skip to phase 4 and do an equivocation check. phase.set(Phase.EquivocationCheck); equivocationCheck(encryptionKeys, newAddresses, true); }
// In the broadcast phase, we have to either receive all the // new addresses or send them all out. This is set off in its own function so that the // malicious machine can override it. Deque<Address> readAndBroadcastNewAddresses(Message shuffled) throws IOException, InterruptedException, TimeoutException, BlameException, FormatException { Deque<Address> newAddresses; if (me == N) { // The last player adds his own new address in without // encrypting anything and shuffles the result. newAddresses = readNewAddresses(shuffled); mailbox.broadcast(shuffled, phase.get()); } else { // All other players just receive their addresses from the last one. newAddresses = readNewAddresses(mailbox.receiveFrom(players.get(N), phase.get())); } return newAddresses; }
final void execute(PhaseId startPhaseId) throws IOException { PhaseId currentPhaseId = startPhaseId; while (currentPhaseId != null) { IPhase currentPhase = findPhase(currentPhaseId); CurrentPhase.set(currentPhaseId); phaseListenerManager.notifyBeforePhase(currentPhaseId, lifecycle); PhaseId nextPhaseId = currentPhase.execute(getDisplay()); phaseListenerManager.notifyAfterPhase(currentPhaseId, lifecycle); currentPhaseId = nextPhaseId; } }
void checkDoubleSpending(Transaction t) throws InterruptedException, IOException, FormatException, TimeoutException, Matrix, CoinNetworkException, AddressFormatException { // Check for double spending. Message doubleSpend = messages.make(); for (VerificationKey key : players.values()) { Transaction o = coin.getConflictingTransaction(t, key.address(), amount); if (o != null) { doubleSpend = doubleSpend.attach(Blame.DoubleSpend(key, o)); } } if (!doubleSpend.isEmpty()) { phase.set(Phase.Blame); mailbox.broadcast(doubleSpend, phase.get()); throw fillBlameMatrix(); } }
// Players run an equivocation check when they must confirm that they all have // the same information. void equivocationCheck( Map<VerificationKey, EncryptionKey> encryptonKeys, Queue<Address> newAddresses, boolean errorCase // There is an equivocation check that occurs ) throws InterruptedException, TimeoutException, Matrix, IOException, FormatException { Message equivocationCheck = equivocationCheckHash(players, encryptonKeys, newAddresses); mailbox.broadcast(equivocationCheck, phase.get()); System.out.println("Player " + me + " equivocation message " + equivocationCheck); // Wait for a similar message from everyone else and check that the result is the name. Map<VerificationKey, Message> hashes = null; hashes = mailbox.receiveFromMultipleBlameless(playerSet(1, players.size()), phase.get()); hashes.put(vk, equivocationCheck); if (areEqual(hashes.values())) { // We may have got this far as part of a normal part of the protocol or as a part // of an error case. If this is a normal part of the protocol, a blame message // having been received indicates that another player has a problem with the // output vector received from the last player in phase 3. if (errorCase || mailbox.blame(Reason.ShuffleFailure) || mailbox.blame(Reason.MissingOutput)) { blameBroadcastShuffleMessages(); } return; } log.warn("Player " + me + " equivocation check fails."); // If the hashes are not equal, enter the blame phase. // Collect all packets from phase 1 and 3. phase.set(Phase.Blame); Queue<Packet> evidence = mailbox.getPacketsByPhase(Phase.Announcement); evidence.addAll(mailbox.getPacketsByPhase(Phase.BroadcastOutput)); mailbox.broadcast(messages.make().attach(Blame.EquivocationFailure(evidence)), phase.get()); throw fillBlameMatrix(); }
// Everyone except player 1 creates a new keypair and sends it around to everyone else. DecryptionKey broadcastNewKey(Map<VerificationKey, Address> changeAddresses) throws TimeoutException, InterruptedException, IOException, FormatException { DecryptionKey dk = null; dk = crypto.makeDecryptionKey(); // Broadcast the public key and store it in the set with everyone else's. encryptionKeys.put(vk, dk.EncryptionKey()); changeAddresses.put(vk, change); Message message = messages.make().attach(dk.EncryptionKey()); if (change != null) { message = message.attach(change); } mailbox.broadcast(message, phase.get()); return dk; }
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; }