// When we know we'll receive a bunch of blame messages, we have to go through them all // to figure out what's going on. final Matrix fillBlameMatrix() throws IOException, InterruptedException, FormatException { Matrix matrix = new Matrix(); Map<VerificationKey, Queue<Packet>> blameMessages = mailbox.receiveAllBlame(); // Get all hashes received in phase 4 to check that they were reported correctly. Map<VerificationKey, Message> hashes = new HashMap<>(); { Queue<Packet> hashMessages = mailbox.getPacketsByPhase(Phase.EquivocationCheck); for (Packet packet : hashMessages) { hashes.put(packet.from(), packet.payload()); } // Include my own hash. hashes.put(vk, equivocationCheckHash(players, encryptionKeys, newAddresses)); } // The messages sent in the broadcast phase by the last player to all the other players. Map<VerificationKey, Packet> outputVectors = new HashMap<>(); // The encryption keys sent from every player to every other. Map<VerificationKey, Map<VerificationKey, EncryptionKey>> sentKeys = new HashMap<>(); // The list of messages history in phase 2. Map<VerificationKey, Packet> shuffleMessages = new HashMap<>(); // The set of decryption keys from each player. Map<VerificationKey, DecryptionKey> decryptionKeys = new HashMap<>(); // Determine who is being blamed and by whom. for (Map.Entry<VerificationKey, Queue<Packet>> entry : blameMessages.entrySet()) { VerificationKey from = entry.getKey(); Queue<Packet> responses = entry.getValue(); for (Packet packet : responses) { Message message = packet.payload(); if (message.isEmpty()) { log.error("Empty blame message received from " + from); matrix.put(vk, Evidence.InvalidFormat(from, packet)); } while (!message.isEmpty()) { Blame blame = message.readBlame(); message = message.rest(); switch (blame.reason) { case InsufficientFunds: { // Is the evidence included sufficient? // TODO check whether this is a conflicting transaction. matrix.put(from, Evidence.InsufficientFunds(blame.accused)); break; } // If there is an equivocation failure, all players must send a blame // message containing the messages they received in phases 1 and 4. case EquivocationFailure: { // These are the keys received by // this player in the announcement phase. Map<VerificationKey, EncryptionKey> receivedKeys = new HashMap<>(); if (!fillBlameMatrixCollectHistory( vk, from, blame.packets, matrix, outputVectors, shuffleMessages, receivedKeys, sentKeys)) { matrix.put(from, Evidence.Liar(from, new Packet[] {packet})); } Queue<Address> addresses = new LinkedList<>(); // The last player will not have // received a separate set of addresses for // us to check, so we insert our own. if (from.equals(players.get(N))) { addresses = newAddresses; } else { Message output = outputVectors.get(from).payload(); while (!output.isEmpty()) { addresses.add(output.readAddress()); output = output.rest(); } } // If the sender is not player one, we add the keys he sent us, // as he would not have received any set of keys from himself. if (!from.equals(players.get(1))) { receivedKeys.put(from, encryptionKeys.get(from)); } // Check if this player correctly reported // the hash previously sent to us. Message newHash = equivocationCheckHash(players, receivedKeys, addresses); if (newHash == null || !hashes.get(from).equals(newHash)) { matrix.put(vk, Evidence.Liar(from, new Packet[] {packet})); } break; } case ShuffleAndEquivocationFailure: { if (!decryptionKeys.containsKey(from)) { // We should not receive two keys from the same player so this // block should always execute in a perfect world. decryptionKeys.put(from, blame.privateKey); } // Check that the decryption key is valid. (The decryption key // can be null for player 1, who doesn't make one.) if (!packet.from().equals(players.get(1))) { if (blame.privateKey == null) { matrix.put(vk, Evidence.InvalidFormat(from, packet)); } else if (!blame.privateKey.EncryptionKey().equals(encryptionKeys.get(from))) { // We have received a private key that does not match // the public key we have for this player. Include the // original packet containing the encryption key and the // packet with the mismatched key. Packet newKeyPacket = null; Queue<Packet> newKeyPackets = mailbox.getPacketsByPhase(Phase.Announcement); for (Packet p : newKeyPackets) { if (p.from().equals(packet.from())) { newKeyPacket = p; } } matrix.put(vk, Evidence.Liar(from, new Packet[] {newKeyPacket, packet})); } } if (!fillBlameMatrixCollectHistory( vk, from, blame.packets, matrix, outputVectors, shuffleMessages, new HashMap<VerificationKey, EncryptionKey>(), sentKeys)) { matrix.put(vk, Evidence.Liar(from, new Packet[] {packet})); } break; } case DoubleSpend: { // TODO does this actually spend the funds? matrix.put(from, Evidence.DoubleSpend(blame.accused, blame.t)); break; } case InvalidSignature: { if (blame.invalid == null || blame.accused == null) { matrix.put(vk, Evidence.Liar(from, new Packet[] {packet})); break; } // TODO do we agree that the signature is invalid? matrix.put(from, Evidence.InvalidSignature(blame.accused, blame.invalid)); break; } // These should already have been handled. case MissingOutput: { break; } case ShuffleFailure: { break; } default: matrix.put(vk, Evidence.Placeholder(from, Reason.InvalidFormat)); } } } } // Check that we have all the required announcement messages. if (sentKeys.size() > 0) { for (int i = 2; i < players.size(); i++) { if (i == me) { continue; } VerificationKey from = players.get(i); Map<VerificationKey, EncryptionKey> sent = sentKeys.get(from); if (sent == null) { // This should not really happen. continue; } // Add in the key I received from this player. sent.put(vk, encryptionKeys.get(from)); EncryptionKey key = null; for (int j = 1; j <= players.size(); j++) { if (i == j) { continue; } VerificationKey to = players.get(j); EncryptionKey next = sent.get(to); if (next == null) { // blame player to. He should have sent us this. matrix.put(vk, Evidence.Placeholder(to, Reason.Liar)); continue; } if (key != null && !key.equals(next)) { matrix.put(vk, Evidence.EquivocationFailureAnnouncement(from, sent)); break; } key = next; } } } boolean outputEquivocate = false; if (outputVectors.size() > 0) { // Add our own vector to this. if (me != N) { outputVectors.put(vk, mailbox.getPacketsByPhase(Phase.BroadcastOutput).peek()); } // We should have one output vector for every player except the last and ourselves. Set<VerificationKey> leftover = playerSet(1, N - 1); leftover.removeAll(outputVectors.keySet()); if (leftover.size() > 0) { for (VerificationKey key : leftover) { // matrix.put(vk, Evidence.Placeholder(key, Reason.Liar)); } } List<Message> outputMessages = new LinkedList<>(); for (Packet packet : outputVectors.values()) { outputMessages.add(packet.payload()); } // If they are not all equal, blame the last player for equivocating. if (!areEqual(outputMessages)) { matrix.put(vk, Evidence.EquivocationFailureBroadcast(players.get(N), outputVectors)); outputEquivocate = true; } } if (decryptionKeys.size() > 0) { // We should have one decryption key for every player except the first. Set<VerificationKey> leftover = playerSet(2, players.size()); leftover.removeAll(decryptionKeys.keySet()); if (leftover.size() > 0) { log.warn("leftover"); // TODO blame someone. } else { Evidence shuffleEvidence = checkShuffleMisbehavior( players, decryptionKeys, shuffleMessages, outputEquivocate ? null : outputVectors); if (shuffleEvidence != null) { matrix.put(vk, shuffleEvidence); } } } return matrix; }
// This function is only called by fillBlameMatrix to collect messages sent in // phases 1, 2, and 3. and to organize the information appropriately. // If the function returns false, this means that packets has an invalid format. private static boolean fillBlameMatrixCollectHistory( VerificationKey vk, VerificationKey from, Queue<Packet> packets, Matrix matrix, // The messages sent in the broadcast phase by the last player to all the other players. Map<VerificationKey, Packet> outputVectors, // The list of messages history in phase 2. Map<VerificationKey, Packet> shuffleMessages, // The keys received by everyone in the announcement phase. Map<VerificationKey, EncryptionKey> receivedKeys, // The keys sent by everyone in the announcement phase. Map<VerificationKey, Map<VerificationKey, EncryptionKey>> sentKeys) throws FormatException { if (packets == null) return false; boolean validPacket = true; // Collect all packets received in the appropriate place. for (Packet packet : packets) { switch (packet.phase()) { case BroadcastOutput: { if (outputVectors.containsKey(from)) { // We should only ever receive one such message from each player. if (outputVectors.containsKey(from) && !outputVectors.get(from).equals(packet)) { log.error( "Player " + vk.toString() + " null blames " + from.toString() + ", case A; " + outputVectors.get(from).toString() + " != " + packet.toString()); matrix.put(vk, Evidence.Liar(from, new Packet[] {outputVectors.get(from), packet})); } } else { outputVectors.put(from, packet); } break; } case Announcement: { Map<VerificationKey, EncryptionKey> map = sentKeys.get(packet.from()); if (map == null) { map = new HashMap<>(); sentKeys.put(packet.from(), map); } EncryptionKey key = packet.payload().readEncryptionKey(); map.put(from, key); receivedKeys.put(packet.from(), key); break; } case Shuffling: { if (shuffleMessages.containsKey(from) && !shuffleMessages.get(from).equals(packet)) { matrix.put(vk, Evidence.Liar(from, new Packet[] {shuffleMessages.get(from), packet})); } else { shuffleMessages.put(from, packet); } break; } default: { // This case should never happen. // It's not malicious but it's not allowed either. validPacket = false; } } } return validPacket; }
private static Evidence checkShuffleMisbehavior( Map<Integer, VerificationKey> players, Map<VerificationKey, DecryptionKey> decryptionKeys, Map<VerificationKey, Packet> shuffleMessages, Map<VerificationKey, Packet> broadcastMessages) throws FormatException { if (players == null || decryptionKeys == null || shuffleMessages == null || broadcastMessages == null) throw new NullPointerException(); SortedSet<String> outputs = new TreeSet<>(); // Go through the steps of shuffling messages. for (int i = 1; i < players.size(); i++) { Packet packet = shuffleMessages.get(players.get(i + 1)); if (packet == null) { // TODO Blame a player for lying. return null; } Message message = packet.payload(); // Grab the correct number of addresses and decrypt them. SortedSet<String> decrypted = new TreeSet<>(); for (int j = 0; j < i; j++) { if (message.isEmpty()) { return Evidence.ShuffleMisbehaviorDropAddress( players.get(i), decryptionKeys, shuffleMessages, broadcastMessages); } String address = message.readString(); message = message.rest(); for (int k = i + 1; k <= players.size(); k++) { address = decryptionKeys.get(players.get(k)).decrypt(address); } // There shouldn't be duplicates. if (decrypted.contains(address)) { return Evidence.ShuffleMisbehaviorDropAddress( players.get(i), decryptionKeys, shuffleMessages, broadcastMessages); } decrypted.add(address); } // Does this contain all the previous addresses? if (!decrypted.containsAll(outputs)) { return Evidence.ShuffleMisbehaviorDropAddress( players.get(i), decryptionKeys, shuffleMessages, broadcastMessages); } decrypted.removeAll(outputs); // There should be one new address. if (decrypted.size() != 1) { return Evidence.ShuffleMisbehaviorDropAddress( players.get(i), decryptionKeys, shuffleMessages, broadcastMessages); } outputs.add(decrypted.first()); } // Now check the last set of messages from player N. // All broadcast messages should have the same content and we should // have already checked for this. Therefore we just look for the first // one that is available. // (The message for player 1 should always be available, so theoretically // we don't need to loop through everybody, but who knows what might have happened.) Packet packet = null; for (int j = 1; j <= players.size(); j++) { packet = broadcastMessages.get(players.get(j)); if (packet != null) { break; } } if (packet == null) { // TODO blame someone. return null; } Message message = packet.payload(); // Grab the correct number of addresses and decrypt them. SortedSet<String> addresses = new TreeSet<>(); for (int j = 0; j < players.size(); j++) { if (message.isEmpty()) { return Evidence.ShuffleMisbehaviorDropAddress( players.get(players.size()), decryptionKeys, shuffleMessages, broadcastMessages); } Address address = message.readAddress(); // There shouldn't be duplicates. if (addresses.contains(address.toString())) { return Evidence.ShuffleMisbehaviorDropAddress( players.get(players.size()), decryptionKeys, shuffleMessages, broadcastMessages); } addresses.add(address.toString()); } // Does this contain all the previous addresses? if (!addresses.containsAll(outputs)) { return Evidence.ShuffleMisbehaviorDropAddress( players.get(players.size()), decryptionKeys, shuffleMessages, broadcastMessages); } addresses.removeAll(outputs); // There should be one new address. if (addresses.size() != 1) { return Evidence.ShuffleMisbehaviorDropAddress( players.get(players.size()), decryptionKeys, shuffleMessages, broadcastMessages); } return null; }