diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 31ff232..b99caa4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -#Thu Dec 17 12:20:25 IST 2015 +#Sun Dec 27 09:28:01 IST 2015 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/meerkat-common/src/main/proto/meerkat/BulletinBoardAPI.proto b/meerkat-common/src/main/proto/meerkat/BulletinBoardAPI.proto index 28fc948..2e2e7e2 100644 --- a/meerkat-common/src/main/proto/meerkat/BulletinBoardAPI.proto +++ b/meerkat-common/src/main/proto/meerkat/BulletinBoardAPI.proto @@ -10,6 +10,9 @@ message BoolMsg { bool value = 1; } +message IntMsg { + int32 value = 1; +} message MessageID { // The ID of a message for unique retrieval. @@ -26,10 +29,10 @@ message UnsignedBulletinBoardMessage { } message BulletinBoardMessage { - + // Serial entry number of message in database int64 entryNum = 1; - + // Unsigned raw data of message UnsignedBulletinBoardMessage msg = 2; @@ -38,9 +41,9 @@ message BulletinBoardMessage { } message BulletinBoardMessageList { - + repeated BulletinBoardMessage message = 1; - + } enum FilterType { @@ -49,13 +52,17 @@ enum FilterType { MAX_ENTRY = 2; // Find all entries in database up to specified entry number (chronological) SIGNER_ID = 3; // Find all entries in database that correspond to specific signature (signer) TAG = 4; // Find all entries in database that have a specific tag + + // NOTE: The MAX_MESSAGES filter must remain the last filter type + // This is because the condition it specifies in an SQL statement must come last in the statement + // Keeping it last here allows for easily sorting the filters and keeping the code general MAX_MESSAGES = 5; // Return at most some specified number of messages } message MessageFilter { - + FilterType type = 1; - + oneof filter{ bytes id = 2; int64 entry = 3; @@ -65,9 +72,36 @@ message MessageFilter { } message MessageFilterList { - + // Combination of filters. // To be implemented using intersection ("AND") operations. repeated MessageFilter filter = 1; - + +} + +// This message is used to start a batch transfer to the Bulletin Board Server +message BeginBatchMessage { + bytes signerId = 1; // Unique signer identifier + int32 batchId = 2; // Unique identifier for the batch (unique per signer) + repeated string tag = 3; // Tags for the batch message +} + +// This message is used to finalize and sign a batch transfer to the Bulletin Board Server +message CloseBatchMessage { + int32 batchId = 1; // Unique identifier for the batch (unique per signer) + int32 batchLength = 2; // Number of messages in the batch + meerkat.Signature sig = 3; // Signature on the (ordered) batch messages +} + +// Container for single batch message data +message BatchData { + bytes data = 1; +} + +// These messages comprise a batch message +message BatchMessage { + bytes signerId = 1; // Unique signer identifier + int32 batchId = 2; // Unique identifier for the batch (unique per signer) + int32 serialNum = 3; // Location of the message in the batch: starting from 0 + BatchData data = 4; // Actual data } \ No newline at end of file diff --git a/meerkat-common/src/main/proto/meerkat/voting.proto b/meerkat-common/src/main/proto/meerkat/voting.proto index beb4e0c..9837cce 100644 --- a/meerkat-common/src/main/proto/meerkat/voting.proto +++ b/meerkat-common/src/main/proto/meerkat/voting.proto @@ -52,6 +52,16 @@ message BallotAnswerTranslationTable { bytes data = 1; } +// Data required in order to access the Bulletin Board Servers +message BulletinBoardClientParams { + + // Addresses of all Bulletin Board Servers + repeated string bulletinBoardAddress = 1; + + // Threshold fraction of successful servers posts before a post task is considered complete + float minRedundancy = 2; +} + message ElectionParams { // TODO: different sets of keys for different roles? repeated SignatureVerificationKey trusteeVerificationKeys = 1; @@ -75,4 +85,6 @@ message ElectionParams { // Translation table between answers and plaintext encoding BallotAnswerTranslationTable answerTranslationTable = 7; + // Data required in order to access the Bulletin Board Servers + BulletinBoardClientParams bulletinBoardClientParams = 8; } diff --git a/mixer/src/main/java/main/MainMixing.java b/mixer/src/main/java/main/MainMixing.java index 25b61df..70423c5 100644 --- a/mixer/src/main/java/main/MainMixing.java +++ b/mixer/src/main/java/main/MainMixing.java @@ -1,11 +1,20 @@ package main; +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; import meerkat.crypto.mixnet.Mix2ZeroKnowledgeVerifier; import meerkat.crypto.mixnet.Mixer; +import meerkat.protobuf.BulletinBoardAPI; import meerkat.protobuf.Crypto; import meerkat.protobuf.Mixing; +import necessary.AsyncBulletinBoardClient; +import necessary.BulletinBoardClient; +import necessary.SignedBatch; import qilin.util.Pair; + +import java.math.BigInteger; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -15,119 +24,247 @@ import java.util.List; */ public class MainMixing { - Mixer mixer; - Mix2ZeroKnowledgeVerifier verifier; - int n,layers; + private Mixer mixer; + private Mix2ZeroKnowledgeVerifier verifier; + private int n, layers; + private AsyncBulletinBoardClient asyncBulletinBoardClient; + private final byte[] id; + private final int mixerOrder; - public MainMixing(Mixer mixer, Mix2ZeroKnowledgeVerifier verifier, int n) { + + public MainMixing(Mixer mixer, int mixerOrder, Mix2ZeroKnowledgeVerifier verifier, int n + , AsyncBulletinBoardClient asyncBulletinBoardClient, byte[] id) { this.mixer = mixer; + this.mixerOrder = mixerOrder; this.verifier = verifier; this.n = n; this.layers = (int) (2 * Math.log(n) / Math.log(2)) - 1; // layers = 2logn -1 + this.asyncBulletinBoardClient = asyncBulletinBoardClient; + this.id = id; } - private void firstMixing( ) throws Exception - { - updateBB(mixer.mix(getInputForFirstMixer())); - } + public void main(List prevBatchIds, int batchId, BulletinBoardClient.ClientCallback callback) throws Exception { - private void otherMixing() throws Exception - { - List zeroKnowledgeProofsLists = downloadProofs(); - List rerandomizableEncryptedMessageLists = downloadEncryptionTable(); - - for (int i = 0; i < zeroKnowledgeProofsLists.size(); i++) - { - if(!verifyTable(n,layers,zeroKnowledgeProofsLists.get(i),rerandomizableEncryptedMessageLists.get(i))) - throw new Exception(); - } - - Crypto.RerandomizableEncryptedMessage[] lastLayerInCurrent,firstLineInNext; - for (int i = 0; i < rerandomizableEncryptedMessageLists.size() - 1 ; i++) - { - lastLayerInCurrent = rerandomizableEncryptedMessageLists.get(i)[layers - 1]; - firstLineInNext = rerandomizableEncryptedMessageLists.get(i + 1)[0]; - if(!Arrays.equals(lastLayerInCurrent,firstLineInNext)) - throw new Exception(); - } - - List inputForMixer = - Arrays.asList(rerandomizableEncryptedMessageLists - .get(rerandomizableEncryptedMessageLists.size() - 1)[layers - 1]); - - updateBB(mixer.mix(inputForMixer)); - } - - private List downloadProofs() - { - return null; - } - - private List downloadEncryptionTable() - { - return null; - } - - private List getInputForFirstMixer() - { - return null; - } - - private void updateBB(Pair mixerOutput){ - - } - - private boolean verifyTable(int n,int layers, Mixing.ZeroKnowledgeProof[][] zeroKnowledgeProofs, - Crypto.RerandomizableEncryptedMessage[][] rerandomizableEncryptedMessages) - { - int index1,index2,layer; - - //initialize locationChecksum table - // use for check BeneshNet validity - boolean[][] locationChecksum = new boolean[layers][n]; - for (boolean[] locationChecksumLayer: locationChecksum) { - Arrays.fill(locationChecksumLayer,false); - } + List mixerInput; - for (Mixing.ZeroKnowledgeProof[] zkpLayer: zeroKnowledgeProofs) { - for (Mixing.ZeroKnowledgeProof zkp: zkpLayer) { - Mixing.ZeroKnowledgeProof.Location location = zkp.getLocation(); - index1 = location.getI(); - index2 = location.getJ(); - layer = location.getLayer(); + if (mixerOrder > 0) { + List batchHandlers = new ArrayList(prevBatchIds.size()); + BatchHandler currentBatchHandler; + for (Integer prevBatchId : prevBatchIds) { + currentBatchHandler = new BatchHandler(n, layers); + asyncBulletinBoardClient.readBatch(id, prevBatchId, currentBatchHandler); + batchHandlers.add(currentBatchHandler); + } - // check location validity - if (layer > layers >> 1) { - if (index2 - index1 != n >> (layers - layer)) - return false; + boolean allDone = false; + while (!allDone) { + try { + Thread.sleep(30); + } catch (InterruptedException e) { + // do nothing } - else{ - if (index2 - index1 != n >> (layer + 1)) - return false; + // check all handlers done + allDone = true; + for (BatchHandler batchHandler : batchHandlers) { + allDone &= batchHandler.done; } - - // mark location in table - locationChecksum[layer][index1] = true; - locationChecksum[layer][index2] = true; - - // verify proof - if(!verifier.verify(rerandomizableEncryptedMessages[index1][layer], - rerandomizableEncryptedMessages[index2][layer], - rerandomizableEncryptedMessages[index1][layer + 1], - rerandomizableEncryptedMessages[index2][layer + 1], - zkp)) - return false; + } + + // assert all handlers succeeded + for (BatchHandler batchHandler : batchHandlers) { + if(batchHandler.e != null) + throw batchHandler.e; + } + + BatchHandler lastBatchHandler = batchHandlers.get(batchHandlers.size() - 1); + mixerInput = lastBatchHandler.getInputForMixer(); + + } else { + // ToDo : handle first mixer case + mixerInput = null; + } + + Pair mixerOutput + = mixer.mix(mixerInput); + updateBB(mixerOutput, batchId, callback); + + } + + + private void updateBB(Pair mixerOutput + , int batchId, BulletinBoardClient.ClientCallback callback) { + + BatchConverter batchConverter = new BatchConverter(); + List batchDataList = batchConverter.mixerOutput2BatchData(mixerOutput); + asyncBulletinBoardClient.postBatch(id, batchId, batchDataList, callback); + } + + private class BatchConverter { + + ByteString IntegerToByteString(int a){ + return ByteString.copyFrom(BigInteger.valueOf(a).toByteArray()); + } + + int ByteString2Integer(ByteString bs) { + return Integer.valueOf(bs.toString()); + } + + List mixerOutput2BatchData + (Pair mixerOutput) { + + List result = new ArrayList(); + + // + + result.add(BulletinBoardAPI.BatchData.newBuilder() + .setData(IntegerToByteString(n)) + .build()); + + for (Mixing.ZeroKnowledgeProof[] zkpLayer : mixerOutput.a) { + for (Mixing.ZeroKnowledgeProof zkp : zkpLayer) { + result.add(BulletinBoardAPI.BatchData.newBuilder() + .setData(zkp.toByteString()) + .build()); + } + } + for (Crypto.RerandomizableEncryptedMessage[] encryptionLayer : mixerOutput.b) { + for (Crypto.RerandomizableEncryptedMessage encryption : encryptionLayer) { + result.add(BulletinBoardAPI.BatchData.newBuilder() + .setData(encryption.toByteString()) + .build()); + } + } + return result; + } + Pair batchDataList2MixerOutput + (List batchDataList) throws Exception { + + if (n != ByteString2Integer(batchDataList.remove(0).getData())){ + throw new Exception(); + } + + int nDiv2 = n >>1; + Mixing.ZeroKnowledgeProof[][] proofs = new Mixing.ZeroKnowledgeProof[layers][nDiv2]; + for (int layer = 0; layer (proofs,encryptions); + + } + + } + + + + private class BatchHandler implements BulletinBoardClient.ClientCallback { + + public Pair mixerOutput; + public boolean done; + private int n,layers; + private Exception e; + + public BatchHandler(int n, int layers) { + this.n = n; + this.layers = layers; + done = false; + e = null; + } + + // convert batch message to MixerInput + // and verify it + @Override + public void handleCallback(SignedBatch msg) { + BatchConverter batchConverter = new BatchConverter(); + try { + mixerOutput = batchConverter.batchDataList2MixerOutput(msg.getBatchDataList()); + done = verifyTable(); + } catch (Exception e) { + this.e = e; + done = true; } } - // verify all necessary locations for BeneshNet were proved - for (boolean[] checksumLayer: locationChecksum) { - for (boolean locationBoolean: checksumLayer) { - if (!locationBoolean) - return false; - } + @Override + public void handleFailure(Throwable t) { + + } + + private boolean verifyTable() + { + int index1,index2,layer; + + //initialize locationChecksum table + // use for check BeneshNet validity + boolean[][] locationChecksum = new boolean[layers][n]; + for (boolean[] locationChecksumLayer: locationChecksum) { + Arrays.fill(locationChecksumLayer,false); + } + + Mixing.ZeroKnowledgeProof[][] zeroKnowledgeProofs = mixerOutput.a; + Crypto.RerandomizableEncryptedMessage[][] rerandomizableEncryptedMessages = mixerOutput.b; + + for (Mixing.ZeroKnowledgeProof[] zkpLayer: zeroKnowledgeProofs) { + for (Mixing.ZeroKnowledgeProof zkp: zkpLayer) { + Mixing.ZeroKnowledgeProof.Location location = zkp.getLocation(); + index1 = location.getI(); + index2 = location.getJ(); + layer = location.getLayer(); + + // check location validity + if (layer > layers >> 1) { + if (index2 - index1 != n >> (layers - layer)) + return false; + } + else{ + if (index2 - index1 != n >> (layer + 1)) + return false; + } + + // mark location in table + locationChecksum[layer][index1] = true; + locationChecksum[layer][index2] = true; + + // verify proof + if(!verifier.verify(rerandomizableEncryptedMessages[index1][layer], + rerandomizableEncryptedMessages[index2][layer], + rerandomizableEncryptedMessages[index1][layer + 1], + rerandomizableEncryptedMessages[index2][layer + 1], + zkp)) + return false; + } + } + + // verify all necessary locations for BeneshNet were proved + for (boolean[] checksumLayer: locationChecksum) { + for (boolean locationBoolean: checksumLayer) { + if (!locationBoolean) + return false; + } + } + return true; + } + + public List getInputForMixer() + { + return Arrays.asList(mixerOutput.b[mixerOutput.b.length - 1]); } - return true; } + } diff --git a/mixer/src/main/java/necessary/AsyncBulletinBoardClient.java b/mixer/src/main/java/necessary/AsyncBulletinBoardClient.java new file mode 100644 index 0000000..b8847f9 --- /dev/null +++ b/mixer/src/main/java/necessary/AsyncBulletinBoardClient.java @@ -0,0 +1,62 @@ +package necessary; + + import meerkat.protobuf.BulletinBoardAPI.*; + + import java.util.List; + +/** + * Created by Arbel Deutsch Peled on 14-Dec-15. + */ +public interface AsyncBulletinBoardClient extends BulletinBoardClient { + + /** + * Post a message to the bulletin board in an asynchronous manner + * @param msg is the message to be posted + * @param callback is a class containing methods to handle the result of the operation + * @return a unique message ID for the message, that can be later used to retrieve the batch + */ + MessageID postMessage(BulletinBoardMessage msg, ClientCallback callback); + + /** + * This method allows for sending large messages as a batch to the bulletin board + * @param signerId is the canonical form for the ID of the sender of this batch + * @param batchId is a unique (per signer) ID for this batch + * @param batchDataList is the (canonically ordered) list of data comprising the batch message + * @param startPosition is the location (in the batch) of the first entry in batchDataList (optionally used to continue interrupted post operations) + * @param callback is a callback function class for handling results of the operation + * @return a unique message ID for the entire message, that can be later used to retrieve the batch + */ + MessageID postBatch(byte[] signerId, int batchId, List batchDataList, int startPosition, ClientCallback callback); + + /** + * Overloading of the postBatch method in which startPosition is set to the default value 0 + */ + MessageID postBatch(byte[] signerId, int batchId, List batchDataList, ClientCallback callback); + + /** + * Check how "safe" a given message is in an asynchronous manner + * The result of the computation is a rank between 0.0 and 1.0 indicating the fraction of servers containing the message + * @param id is the unique message identifier for retrieval + * @param callback is a callback function class for handling results of the operation + */ + void getRedundancy(MessageID id, ClientCallback callback); + + /** + * Read all messages posted matching the given filter in an asynchronous manner + * Note that if messages haven't been "fully posted", this might return a different + * set of messages in different calls. However, messages that are fully posted + * are guaranteed to be included. + * @param filterList return only messages that match the filters (null means no filtering). + * @param callback is a callback function class for handling results of the operation + */ + void readMessages(MessageFilterList filterList, ClientCallback> callback); + + /** + * Read a given batch message from the bulletin board + * @param signerId is the ID of the signer (sender) of the batch message + * @param batchId is the unique (per signer) ID of the batch + * @param callback is a callback class for handling the result of the operation + */ + void readBatch(byte[] signerId, int batchId, ClientCallback callback); + +} diff --git a/mixer/src/main/java/necessary/BulletinBoardClient.java b/mixer/src/main/java/necessary/BulletinBoardClient.java new file mode 100644 index 0000000..6442c40 --- /dev/null +++ b/mixer/src/main/java/necessary/BulletinBoardClient.java @@ -0,0 +1,63 @@ +package necessary; + +/** + * Created by Tzlil on 12/27/2015. + */ + + import meerkat.comm.CommunicationException; + import meerkat.protobuf.Voting.*; + + import static meerkat.protobuf.BulletinBoardAPI.*; + + import java.util.List; + +/** + * Created by talm on 24/10/15. + */ +public interface BulletinBoardClient { + + interface ClientCallback { + void handleCallback(T msg); + void handleFailure(Throwable t); + } + + /** + * Initialize the client to use some specified servers + * @param clientParams contains the parameters required for the client setup + */ + void init(BulletinBoardClientParams clientParams); + + /** + * Post a message to the bulletin board in a synchronous manner + * @param msg is the message to be posted + * @return a unique message ID for the message, that can be later used to retrieve the batch + * @throws CommunicationException + */ + MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException; + + + + /** + * Check how "safe" a given message is in a synchronous manner + * @param id is the unique message identifier for retrieval + * @return a normalized "redundancy score" from 0 (local only) to 1 (fully published) + */ + float getRedundancy(MessageID id); + + /** + * Read all messages posted matching the given filter in a synchronous manner + * Note that if messages haven't been "fully posted", this might return a different + * set of messages in different calls. However, messages that are fully posted + * are guaranteed to be included. + * @param filterList return only messages that match the filters (null means no filtering). + * @return the list of messages + */ + List readMessages(MessageFilterList filterList); + + /** + * Closes all connections, if any. + * This is done in a synchronous (blocking) way. + */ + void close(); + +} diff --git a/mixer/src/main/java/necessary/SignedBatch.java b/mixer/src/main/java/necessary/SignedBatch.java new file mode 100644 index 0000000..6aecebf --- /dev/null +++ b/mixer/src/main/java/necessary/SignedBatch.java @@ -0,0 +1,54 @@ +package necessary; + +/** + * Created by Tzlil on 12/27/2015. + */ + + import meerkat.protobuf.BulletinBoardAPI.*; + import meerkat.protobuf.Crypto.*; + + import java.util.LinkedList; + import java.util.List; + +/** + * Created by Arbel Deutsch Peled on 14-Dec-15. + * + * A data structure for holding both a batch message and its signature + */ +public class SignedBatch { + + private List batchDataList; + private Signature signature; + + public SignedBatch() { + batchDataList = new LinkedList(); + } + + public SignedBatch(List newDataList) { + this(); + appendBatchData(newDataList); + } + + public SignedBatch(List newDataList, Signature newSignature) { + this(newDataList); + signature = newSignature; + } + + public List getBatchDataList() { + return batchDataList; + } + + public Signature getSignature() { + return signature; + } + + public void appendBatchData(BatchData newBatchData) { + batchDataList.add(newBatchData); + } + + public void appendBatchData(List newBatchDataList) { + batchDataList.addAll(newBatchDataList); + } + + +}