diff --git a/.gitignore b/.gitignore index 6339218..a14486c 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ out *.prefs *.project *.classpath -bulletin-board-server/local-instances/meerkat.db +*.db +*.sql diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/BatchDataContainer.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/BatchDataContainer.java index 53f4870..884aad7 100644 --- a/bulletin-board-client/src/main/java/meerkat/bulletinboard/BatchDataContainer.java +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/BatchDataContainer.java @@ -1,7 +1,6 @@ package meerkat.bulletinboard; -import com.google.protobuf.ByteString; -import meerkat.protobuf.BulletinBoardAPI.BatchData; +import meerkat.protobuf.BulletinBoardAPI.BatchChunk; import java.util.List; @@ -13,13 +12,13 @@ public class BatchDataContainer { public final byte[] signerId; public final int batchId; - public final List batchDataList; + public final List batchChunkList; public final int startPosition; - public BatchDataContainer(byte[] signerId, int batchId, List batchDataList, int startPosition) { + public BatchDataContainer(byte[] signerId, int batchId, List batchChunkList, int startPosition) { this.signerId = signerId; this.batchId = batchId; - this.batchDataList = batchDataList; + this.batchChunkList = batchChunkList; this.startPosition = startPosition; } } diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/CachedBulletinBoardClient.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/CachedBulletinBoardClient.java index 15beec7..3e1ed48 100644 --- a/bulletin-board-client/src/main/java/meerkat/bulletinboard/CachedBulletinBoardClient.java +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/CachedBulletinBoardClient.java @@ -162,13 +162,13 @@ public class CachedBulletinBoardClient implements SubscriptionBulletinBoardClien } @Override - public void postBatchData(final byte[] signerId, final int batchId, final List batchDataList, + public void postBatchData(final byte[] signerId, final int batchId, final List batchChunkList, final int startPosition, final FutureCallback callback) { - localClient.postBatchData(signerId, batchId, batchDataList, startPosition, new FutureCallback() { + localClient.postBatchData(signerId, batchId, batchChunkList, startPosition, new FutureCallback() { @Override public void onSuccess(Boolean result) { - remoteClient.postBatchData(signerId, batchId, batchDataList, startPosition, callback); + remoteClient.postBatchData(signerId, batchId, batchChunkList, startPosition, callback); } @Override @@ -181,12 +181,12 @@ public class CachedBulletinBoardClient implements SubscriptionBulletinBoardClien } @Override - public void postBatchData(final byte[] signerId, final int batchId, final List batchDataList, final FutureCallback callback) { + public void postBatchData(final byte[] signerId, final int batchId, final List batchChunkList, final FutureCallback callback) { - localClient.postBatchData(signerId, batchId, batchDataList, new FutureCallback() { + localClient.postBatchData(signerId, batchId, batchChunkList, new FutureCallback() { @Override public void onSuccess(Boolean result) { - remoteClient.postBatchData(signerId, batchId, batchDataList, callback); + remoteClient.postBatchData(signerId, batchId, batchChunkList, callback); } @Override @@ -199,13 +199,13 @@ public class CachedBulletinBoardClient implements SubscriptionBulletinBoardClien } @Override - public void postBatchData(final ByteString signerId, final int batchId, final List batchDataList, + public void postBatchData(final ByteString signerId, final int batchId, final List batchChunkList, final int startPosition, final FutureCallback callback) { - localClient.postBatchData(signerId, batchId, batchDataList, startPosition, new FutureCallback() { + localClient.postBatchData(signerId, batchId, batchChunkList, startPosition, new FutureCallback() { @Override public void onSuccess(Boolean result) { - remoteClient.postBatchData(signerId, batchId, batchDataList, startPosition, callback); + remoteClient.postBatchData(signerId, batchId, batchChunkList, startPosition, callback); } @Override @@ -218,12 +218,12 @@ public class CachedBulletinBoardClient implements SubscriptionBulletinBoardClien } @Override - public void postBatchData(final ByteString signerId, final int batchId, final List batchDataList, final FutureCallback callback) { + public void postBatchData(final ByteString signerId, final int batchId, final List batchChunkList, final FutureCallback callback) { - localClient.postBatchData(signerId, batchId, batchDataList, new FutureCallback() { + localClient.postBatchData(signerId, batchId, batchChunkList, new FutureCallback() { @Override public void onSuccess(Boolean result) { - remoteClient.postBatchData(signerId, batchId, batchDataList, callback); + remoteClient.postBatchData(signerId, batchId, batchChunkList, callback); } @Override diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/LocalBulletinBoardClient.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/LocalBulletinBoardClient.java index 0bc03ee..d982dd2 100644 --- a/bulletin-board-client/src/main/java/meerkat/bulletinboard/LocalBulletinBoardClient.java +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/LocalBulletinBoardClient.java @@ -92,7 +92,7 @@ public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBo .setBatchId(completeBatch.getBeginBatchMessage().getBatchId()); int i=0; - for (BatchData data : completeBatch.getBatchDataList()){ + for (BatchChunk data : completeBatch.getBatchDataList()){ server.postBatchMessage(builder.setSerialNum(i).setData(data).build()); @@ -100,7 +100,7 @@ public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBo } - return server.closeBatchMessage(completeBatch.getCloseBatchMessage()).getValue(); + return server.closeBatch(completeBatch.getCloseBatchMessage()).getValue(); } } @@ -140,13 +140,13 @@ public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBo private final ByteString signerId; private final int batchId; - private final List batchDataList; + private final List batchChunkList; private final int startPosition; - public BatchDataPoster(ByteString signerId, int batchId, List batchDataList, int startPosition) { + public BatchDataPoster(ByteString signerId, int batchId, List batchChunkList, int startPosition) { this.signerId = signerId; this.batchId = batchId; - this.batchDataList = batchDataList; + this.batchChunkList = batchChunkList; this.startPosition = startPosition; } @@ -159,7 +159,7 @@ public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBo .setBatchId(batchId); int i = startPosition; - for (BatchData data : batchDataList){ + for (BatchChunk data : batchChunkList){ msgBuilder.setSerialNum(i) .setData(data); @@ -178,23 +178,23 @@ public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBo } @Override - public void postBatchData(byte[] signerId, int batchId, List batchDataList, int startPosition, FutureCallback callback) { - postBatchData(ByteString.copyFrom(signerId), batchId, batchDataList, startPosition, callback); + public void postBatchData(byte[] signerId, int batchId, List batchChunkList, int startPosition, FutureCallback callback) { + postBatchData(ByteString.copyFrom(signerId), batchId, batchChunkList, startPosition, callback); } @Override - public void postBatchData(byte[] signerId, int batchId, List batchDataList, FutureCallback callback) { - postBatchData(signerId, batchId, batchDataList, 0, callback); + public void postBatchData(byte[] signerId, int batchId, List batchChunkList, FutureCallback callback) { + postBatchData(signerId, batchId, batchChunkList, 0, callback); } @Override - public void postBatchData(ByteString signerId, int batchId, List batchDataList, int startPosition, FutureCallback callback) { - Futures.addCallback(executorService.submit(new BatchDataPoster(signerId, batchId, batchDataList, startPosition)), callback); + public void postBatchData(ByteString signerId, int batchId, List batchChunkList, int startPosition, FutureCallback callback) { + Futures.addCallback(executorService.submit(new BatchDataPoster(signerId, batchId, batchChunkList, startPosition)), callback); } @Override - public void postBatchData(ByteString signerId, int batchId, List batchDataList, FutureCallback callback) { - postBatchData(signerId, batchId, batchDataList, 0, callback); + public void postBatchData(ByteString signerId, int batchId, List batchChunkList, FutureCallback callback) { + postBatchData(signerId, batchId, batchChunkList, 0, callback); } private class BatchCloser implements Callable { @@ -208,7 +208,7 @@ public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBo @Override public Boolean call() throws Exception { - return server.closeBatchMessage(msg).getValue(); + return server.closeBatch(msg).getValue(); } } @@ -287,7 +287,7 @@ public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBo } - private class BatchDataReader implements Callable> { + private class BatchDataReader implements Callable> { private final BatchSpecificationMessage batchSpecificationMessage; @@ -296,16 +296,16 @@ public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBo } @Override - public List call() throws Exception { + public List call() throws Exception { ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream(); - MessageOutputStream outputStream = new MessageOutputStream<>(byteOutputStream); + MessageOutputStream outputStream = new MessageOutputStream<>(byteOutputStream); server.readBatch(batchSpecificationMessage, outputStream); - MessageInputStream inputStream = + MessageInputStream inputStream = MessageInputStreamFactory.createMessageInputStream( new ByteArrayInputStream(byteOutputStream.toByteArray()), - BatchData.class); + BatchChunk.class); return inputStream.asList(); @@ -387,7 +387,7 @@ public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBo subscribe(filterList, 0, callback); } - private class CompleteBatchReader implements Callable { + private class CompleteBatchReader implements Callable { private final BatchSpecificationMessage batchSpecificationMessage; @@ -397,7 +397,7 @@ public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBo @Override - public CompleteBatch call() throws Exception { + public BulletinBoardMessage call() throws Exception { final String[] TAGS_TO_REMOVE = {BulletinBoardConstants.BATCH_TAG, BulletinBoardConstants.BATCH_ID_TAG_PREFIX}; @@ -407,13 +407,13 @@ public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBo .build()); ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream(); - MessageOutputStream batchOutputStream = new MessageOutputStream<>(byteOutputStream); + MessageOutputStream batchOutputStream = new MessageOutputStream<>(byteOutputStream); server.readBatch(batchSpecificationMessage,batchOutputStream); - MessageInputStream batchInputStream = + MessageInputStream batchInputStream = MessageInputStreamFactory.createMessageInputStream( new ByteArrayInputStream(byteOutputStream.toByteArray()), - BatchData.class); + BatchChunk.class); completeBatch.appendBatchData(batchInputStream.asList()); @@ -462,7 +462,7 @@ public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBo } @Override - public void readBatch(BatchSpecificationMessage batchSpecificationMessage, FutureCallback callback) { + public void readBatch(BatchSpecificationMessage batchSpecificationMessage, FutureCallback callback) { Futures.addCallback(executorService.submit(new CompleteBatchReader(batchSpecificationMessage)), callback); } @@ -568,8 +568,8 @@ public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBo try { - List batchDataList = batchDataReader.call(); - return new CompleteBatch(batchMessage, batchDataList); + List batchChunkList = batchDataReader.call(); + return new CompleteBatch(batchMessage, batchChunkList); } catch (Exception e) { throw new CommunicationException("Error reading batch"); diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/SimpleBulletinBoardClient.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/SimpleBulletinBoardClient.java index e8fcea0..a93a0d2 100644 --- a/bulletin-board-client/src/main/java/meerkat/bulletinboard/SimpleBulletinBoardClient.java +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/SimpleBulletinBoardClient.java @@ -1,5 +1,6 @@ package meerkat.bulletinboard; +import com.google.protobuf.BoolValue; import com.google.protobuf.ByteString; import meerkat.bulletinboard.workers.singleserver.*; import meerkat.comm.CommunicationException; @@ -70,18 +71,13 @@ public class SimpleBulletinBoardClient implements BulletinBoardClient{ // Only consider valid responses if (response.getStatusInfo() == Response.Status.OK || response.getStatusInfo() == Response.Status.CREATED) { - response.readEntity(BoolMsg.class).getValue(); + response.readEntity(BoolValue.class).getValue(); } else { throw new CommunicationException("Server returned error. Status was: " + response.getStatus()); } } } catch (Exception e) { // Occurs only when server replies with valid status but invalid data - - throw new CommunicationException("Server returned invalid data type: " + e.getMessage()); - - } finally { - if (response != null) - response.close(); + throw new CommunicationException("Error accessing database: " + e.getMessage()); } // Calculate the correct message ID and return it @@ -107,12 +103,12 @@ public class SimpleBulletinBoardClient implements BulletinBoardClient{ BatchMessage.Builder builder = BatchMessage.newBuilder().setSignerId(signerID).setBatchId(batchID); - for (BatchData batchData : completeBatch.getBatchDataList()) { + for (BatchChunk batchChunk : completeBatch.getBatchDataList()) { SingleServerPostBatchWorker postBatchWorker = new SingleServerPostBatchWorker( db, - builder.setData(batchData).setSerialNum(pos).build(), + builder.setData(batchChunk).setSerialNum(pos).build(), 0); postBatchWorker.call(); @@ -187,6 +183,7 @@ public class SimpleBulletinBoardClient implements BulletinBoardClient{ for (String db : meerkatDBs) { try { + webTarget = client.target(db).path(BULLETIN_BOARD_SERVER_PATH).path(READ_MESSAGES_PATH); SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(db, filterList, 0); @@ -235,9 +232,9 @@ public class SimpleBulletinBoardClient implements BulletinBoardClient{ SingleServerReadBatchWorker batchWorker = new SingleServerReadBatchWorker(db, batchSpecificationMessage, 0); - List batchDataList = batchWorker.call(); + List batchChunkList = batchWorker.call(); - CompleteBatch result = new CompleteBatch(batchMessage, batchDataList); + CompleteBatch result = new CompleteBatch(batchMessage, batchChunkList); return result; diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/SingleServerBulletinBoardClient.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/SingleServerBulletinBoardClient.java index 74a8162..6ef013f 100644 --- a/bulletin-board-client/src/main/java/meerkat/bulletinboard/SingleServerBulletinBoardClient.java +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/SingleServerBulletinBoardClient.java @@ -6,12 +6,9 @@ import com.google.common.util.concurrent.ListeningScheduledExecutorService; import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.ByteString; import meerkat.bulletinboard.workers.singleserver.*; -import meerkat.comm.CommunicationException; import meerkat.protobuf.BulletinBoardAPI.*; import meerkat.protobuf.Voting.BulletinBoardClientParams; -import meerkat.util.BulletinBoardUtils; -import java.util.Arrays; import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -174,7 +171,7 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i private final FutureCallback callback; - private List batchDataList; + private List batchChunkList; private BulletinBoardMessage batchMessage; private AtomicInteger remainingQueries; @@ -198,7 +195,7 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i if (remainingQueries.decrementAndGet() == 0){ if (callback != null) - callback.onSuccess(new CompleteBatch(batchMessage, batchDataList)); + callback.onSuccess(new CompleteBatch(batchMessage, batchChunkList)); } } @@ -213,12 +210,12 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i /** * @return a FutureCallback for the Batch Data List that ties to this object */ - public FutureCallback> asBatchDataListFutureCallback() { - return new FutureCallback>() { + public FutureCallback> asBatchDataListFutureCallback() { + return new FutureCallback>() { @Override - public void onSuccess(List result) { - batchDataList = result; + public void onSuccess(List result) { + batchChunkList = result; combineAndReturn(); } @@ -449,7 +446,7 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i } @Override - public void postBatchData(ByteString signerId, int batchId, List batchDataList, + public void postBatchData(ByteString signerId, int batchId, List batchChunkList, int startPosition, FutureCallback callback) { BatchMessage.Builder builder = BatchMessage.newBuilder() @@ -458,11 +455,11 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i // Create a unified callback to aggregate successful posts - PostBatchDataListCallback listCallback = new PostBatchDataListCallback(batchDataList.size(), callback); + PostBatchDataListCallback listCallback = new PostBatchDataListCallback(batchChunkList.size(), callback); // Iterate through data list - for (BatchData data : batchDataList) { + for (BatchChunk data : batchChunkList) { builder.setSerialNum(startPosition).setData(data); // Create worker with redundancy 1 and MAX_RETRIES retries @@ -479,24 +476,24 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i } @Override - public void postBatchData(ByteString signerId, int batchId, List batchDataList, FutureCallback callback) { + public void postBatchData(ByteString signerId, int batchId, List batchChunkList, FutureCallback callback) { - postBatchData(signerId, batchId, batchDataList, 0, callback); + postBatchData(signerId, batchId, batchChunkList, 0, callback); } @Override - public void postBatchData(byte[] signerId, int batchId, List batchDataList, + public void postBatchData(byte[] signerId, int batchId, List batchChunkList, int startPosition, FutureCallback callback) { - postBatchData(ByteString.copyFrom(signerId), batchId, batchDataList, startPosition, callback); + postBatchData(ByteString.copyFrom(signerId), batchId, batchChunkList, startPosition, callback); } @Override - public void postBatchData(byte[] signerId, int batchId, List batchDataList, FutureCallback callback) { + public void postBatchData(byte[] signerId, int batchId, List batchChunkList, FutureCallback callback) { - postBatchData(signerId, batchId, batchDataList, 0, callback); + postBatchData(signerId, batchId, batchChunkList, 0, callback); } diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/ThreadedBulletinBoardClient.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/ThreadedBulletinBoardClient.java index c53afe7..289ffcf 100644 --- a/bulletin-board-client/src/main/java/meerkat/bulletinboard/ThreadedBulletinBoardClient.java +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/ThreadedBulletinBoardClient.java @@ -127,10 +127,10 @@ public class ThreadedBulletinBoardClient extends SimpleBulletinBoardClient imple } @Override - public void postBatchData(byte[] signerId, int batchId, List batchDataList, + public void postBatchData(byte[] signerId, int batchId, List batchChunkList, int startPosition, FutureCallback callback) { - BatchDataContainer batchDataContainer = new BatchDataContainer(signerId, batchId, batchDataList, startPosition); + BatchDataContainer batchDataContainer = new BatchDataContainer(signerId, batchId, batchChunkList, startPosition); // Create job MultiServerPostBatchDataWorker worker = @@ -142,24 +142,24 @@ public class ThreadedBulletinBoardClient extends SimpleBulletinBoardClient imple } @Override - public void postBatchData(byte[] signerId, int batchId, List batchDataList, FutureCallback callback) { + public void postBatchData(byte[] signerId, int batchId, List batchChunkList, FutureCallback callback) { - postBatchData(signerId, batchId, batchDataList, 0, callback); + postBatchData(signerId, batchId, batchChunkList, 0, callback); } @Override - public void postBatchData(ByteString signerId, int batchId, List batchDataList, + public void postBatchData(ByteString signerId, int batchId, List batchChunkList, int startPosition, FutureCallback callback) { - postBatchData(signerId.toByteArray(), batchId, batchDataList, startPosition, callback); + postBatchData(signerId.toByteArray(), batchId, batchChunkList, startPosition, callback); } @Override - public void postBatchData(ByteString signerId, int batchId, List batchDataList, FutureCallback callback) { + public void postBatchData(ByteString signerId, int batchId, List batchChunkList, FutureCallback callback) { - postBatchData(signerId, batchId, batchDataList, 0, callback); + postBatchData(signerId, batchId, batchChunkList, 0, callback); } @@ -251,3 +251,4 @@ public class ThreadedBulletinBoardClient extends SimpleBulletinBoardClient imple } } +==== BASE ==== diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerPostBatchDataWorker.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerPostBatchDataWorker.java index ef1c4fa..ca7b4fd 100644 --- a/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerPostBatchDataWorker.java +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerPostBatchDataWorker.java @@ -21,7 +21,7 @@ public class MultiServerPostBatchDataWorker extends MultiServerGenericPostWorker @Override protected void doPost(SingleServerBulletinBoardClient client, BatchDataContainer payload) { - client.postBatchData(payload.signerId, payload.batchId, payload.batchDataList, payload.startPosition, this); + client.postBatchData(payload.signerId, payload.batchId, payload.batchChunkList, payload.startPosition, this); } diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerPostBatchWorker.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerPostBatchWorker.java index 763485f..5b5198c 100644 --- a/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerPostBatchWorker.java +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/multiserver/MultiServerPostBatchWorker.java @@ -1,7 +1,6 @@ package meerkat.bulletinboard.workers.multiserver; import com.google.common.util.concurrent.FutureCallback; -import meerkat.bulletinboard.CompleteBatch; import meerkat.bulletinboard.SingleServerBulletinBoardClient; import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage; diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerCloseBatchWorker.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerCloseBatchWorker.java index ab298a5..83e27c2 100644 --- a/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerCloseBatchWorker.java +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerCloseBatchWorker.java @@ -6,7 +6,7 @@ import static meerkat.bulletinboard.BulletinBoardConstants.CLOSE_BATCH_PATH; /** * Created by Arbel Deutsch Peled on 27-Dec-15. - * Tries to contact server once and perform a close batch operation + * Tries to contact server once and perform a stop batch operation */ public class SingleServerCloseBatchWorker extends SingleServerGenericPostWorker { diff --git a/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerGenericPostWorker.java b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerGenericPostWorker.java index 621a828..08a66d1 100644 --- a/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerGenericPostWorker.java +++ b/bulletin-board-client/src/main/java/meerkat/bulletinboard/workers/singleserver/SingleServerGenericPostWorker.java @@ -1,8 +1,9 @@ package meerkat.bulletinboard.workers.singleserver; +import com.google.protobuf.BoolValue; import meerkat.bulletinboard.SingleServerWorker; import meerkat.comm.CommunicationException; -import meerkat.protobuf.BulletinBoardAPI.BoolMsg; +import meerkat.protobuf.Comm.*; import meerkat.rest.Constants; import javax.ws.rs.ProcessingException; @@ -43,8 +44,8 @@ public class SingleServerGenericPostWorker extends SingleServerWorker> { +public class SingleServerReadBatchWorker extends SingleServerWorker> { public SingleServerReadBatchWorker(String serverAddress, BatchSpecificationMessage payload, int maxRetry) { super(serverAddress, payload, maxRetry); @@ -39,7 +34,7 @@ public class SingleServerReadBatchWorker extends SingleServerWorker call() throws CommunicationException{ + public List call() throws CommunicationException{ Client client = clientLocal.get(); @@ -50,11 +45,11 @@ public class SingleServerReadBatchWorker extends SingleServerWorker inputStream = null; + MessageInputStream inputStream = null; try { - inputStream = MessageInputStream.MessageInputStreamFactory.createMessageInputStream(in, BatchData.class); + inputStream = MessageInputStream.MessageInputStreamFactory.createMessageInputStream(in, BatchChunk.class); return inputStream.asList(); diff --git a/bulletin-board-client/src/test/java/meerkat/bulletinboard/GenericBulletinBoardClientTester.java b/bulletin-board-client/src/test/java/meerkat/bulletinboard/GenericBulletinBoardClientTester.java index 88fb22c..fa708d5 100644 --- a/bulletin-board-client/src/test/java/meerkat/bulletinboard/GenericBulletinBoardClientTester.java +++ b/bulletin-board-client/src/test/java/meerkat/bulletinboard/GenericBulletinBoardClientTester.java @@ -8,7 +8,6 @@ import meerkat.crypto.concrete.ECDSASignature; import meerkat.protobuf.BulletinBoardAPI.*; import meerkat.protobuf.Crypto; import meerkat.util.BulletinBoardMessageComparator; -import meerkat.util.BulletinBoardMessageGenerator; import java.io.IOException; import java.io.InputStream; @@ -266,11 +265,11 @@ public class GenericBulletinBoardClientTester { for (int i = 0 ; i < length ; i++){ - BatchData batchData = BatchData.newBuilder() + BatchChunk batchChunk = BatchChunk.newBuilder() .setData(ByteString.copyFrom(randomByteArray(i))) .build(); - completeBatch.appendBatchData(batchData); + completeBatch.appendBatchData(batchChunk); } @@ -508,7 +507,7 @@ public class GenericBulletinBoardClientTester { .build()) .build(); - // Try to close the (unopened) batch; + // Try to stop the (unopened) batch; bulletinBoardClient.closeBatch(closeBatchMessage, failPostCallback); diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/BulletinBoardSQLServer.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/BulletinBoardSQLServer.java index 49ceb7b..6661491 100644 --- a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/BulletinBoardSQLServer.java +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/BulletinBoardSQLServer.java @@ -1,5 +1,7 @@ package meerkat.bulletinboard.sqlserver; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.sql.*; import java.util.*; @@ -8,13 +10,12 @@ import com.google.protobuf.*; import com.google.protobuf.Timestamp; import meerkat.bulletinboard.*; import meerkat.bulletinboard.sqlserver.mappers.*; -import static meerkat.bulletinboard.BulletinBoardConstants.*; import meerkat.comm.CommunicationException; +import meerkat.comm.MessageInputStream; import meerkat.comm.MessageOutputStream; import meerkat.crypto.DigitalSignature; -import meerkat.crypto.concrete.ECDSASignature; import meerkat.crypto.concrete.SHA256Digest; import meerkat.protobuf.BulletinBoardAPI.*; @@ -127,7 +128,17 @@ public class BulletinBoardSQLServer implements DeletableBulletinBoardServer{ new int[] {Types.BLOB, Types.INTEGER} ), + CHECK_BATCH_OPEN( + new String[] {"SignerId", "BatchId"}, + new int[] {Types.BLOB, Types.INTEGER} + ), + GET_BATCH_MESSAGE_DATA( + new String[] {"MsgId", "StartPosition"}, + new int[] {Types.BLOB, Types.INTEGER} + ), + + GET_BATCH_MESSAGE_DATA_BY_IDS( new String[] {"SignerId", "BatchId", "StartPosition"}, new int[] {Types.BLOB, Types.INTEGER, Types.INTEGER} ), @@ -150,6 +161,16 @@ public class BulletinBoardSQLServer implements DeletableBulletinBoardServer{ REMOVE_BATCH_TAGS( new String[] {"SignerId", "BatchId"}, new int[] {Types.BLOB, Types.INTEGER} + ), + + REMOVE_BATCH_IDS( + new String[] {"SignerId", "BatchId"}, + new int[] {Types.BLOB, Types.INTEGER} + ), + + ADD_ENTRY_NUM_TO_BATCH( + new String[] {"SignerId", "BatchId", "EntryNum"}, + new int[] {Types.BLOB, Types.INTEGER, Types.INTEGER} ); private String[] paramNames; @@ -365,7 +386,6 @@ public class BulletinBoardSQLServer implements DeletableBulletinBoardServer{ // TODO write signature reading part. digest = new GenericBulletinBoardDigest(new SHA256Digest()); - signer = new GenericBatchDigitalSignature(new ECDSASignature()); jdbcTemplate = new NamedParameterJdbcTemplate(sqlQueryProvider.getDataSource()); @@ -411,12 +431,12 @@ public class BulletinBoardSQLServer implements DeletableBulletinBoardServer{ } /** - * This procedure is used to convert a boolean to a BoolMsg. + * This procedure is used to convert a boolean to a BoolValue. * @param b is the boolean to convert. * @return a ProtoBuf message with boolean payload. */ - private BoolMsg boolToBoolMsg(boolean b){ - return BoolMsg.newBuilder() + private BoolValue boolToBoolValue(boolean b){ + return BoolValue.newBuilder() .setValue(b) .build(); } @@ -425,14 +445,17 @@ public class BulletinBoardSQLServer implements DeletableBulletinBoardServer{ /** * This method posts a messages to the server * @param msg is the message to post - * @param checkSignature decides whether ot not the method should check the signature before it posts the message - * @return TRUE if the post is successful and FALSE otherwise + * @param precalculatedMsgID is an optional precalculated message ID + * It is used when the message is the stub of a batch message + * In this case the validity of the signature is not checked either + * @return -1 if the message is not verified + * The entry number of the message if the message is posted * @throws CommunicationException */ - public BoolMsg postMessage(BulletinBoardMessage msg, boolean checkSignature) throws CommunicationException{ + private long postMessage(BulletinBoardMessage msg, byte[] precalculatedMsgID) throws CommunicationException{ - if (checkSignature && !verifyMessage(msg)) { - return boolToBoolMsg(false); + if (precalculatedMsgID != null && !verifyMessage(msg)) { + return -1; } String sql; @@ -447,12 +470,16 @@ public class BulletinBoardSQLServer implements DeletableBulletinBoardServer{ List signatureList; Signature[] signatures; - // Calculate message ID (depending only on the the unsigned message) - digest.reset(); - digest.update(msg.getMsg()); + if (precalculatedMsgID != null){ + msgID = precalculatedMsgID; + } else{ + // Calculate message ID (depending only on the the unsigned message) + digest.reset(); + digest.update(msg.getMsg()); - msgID = digest.digest(); + msgID = digest.digest(); + } // Add message to table if needed and store entry number of message. @@ -530,17 +557,24 @@ public class BulletinBoardSQLServer implements DeletableBulletinBoardServer{ jdbcTemplate.batchUpdate(sql,namedParameterArray); - return boolToBoolMsg(true); + return entryNum; } @Override - public BoolMsg postMessage(BulletinBoardMessage msg) throws CommunicationException { - return postMessage(msg, true); // Perform a post and check the signature for authenticity + public BoolValue postMessage(BulletinBoardMessage msg) throws CommunicationException { + + // Perform a post, calculate the message ID and check the signature for authenticity + if (postMessage(msg, null) != -1){ + return BoolValue.newBuilder().setValue(true).build(); // Message was posted + } + + return BoolValue.newBuilder().setValue(false).build(); // Message was not posted + } @Override - public BoolMsg deleteMessage(MessageID msgID) throws CommunicationException { + public BoolValue deleteMessage(MessageID msgID) throws CommunicationException { String sql = sqlQueryProvider.getSQLString(QueryType.DELETE_MSG_BY_ID); Map namedParameters = new HashMap(); @@ -551,12 +585,12 @@ public class BulletinBoardSQLServer implements DeletableBulletinBoardServer{ //TODO: Log - return BoolMsg.newBuilder().setValue(affectedRows > 0).build(); + return BoolValue.newBuilder().setValue(affectedRows > 0).build(); } @Override - public BoolMsg deleteMessage(long entryNum) throws CommunicationException { + public BoolValue deleteMessage(long entryNum) throws CommunicationException { String sql = sqlQueryProvider.getSQLString(QueryType.DELETE_MSG_BY_ENTRY); Map namedParameters = new HashMap(); @@ -567,7 +601,7 @@ public class BulletinBoardSQLServer implements DeletableBulletinBoardServer{ //TODO: Log - return BoolMsg.newBuilder().setValue(affectedRows > 0).build(); + return BoolValue.newBuilder().setValue(affectedRows > 0).build(); } @@ -653,6 +687,32 @@ public class BulletinBoardSQLServer implements DeletableBulletinBoardServer{ } + /** + * Private implementation of the message reader for returning result as a list + * @param filterList is a filter list that defines which messages the client is interested in + * @return the requested list of messages + */ + private List readMessages(MessageFilterList filterList) throws CommunicationException { + + try { + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + readMessages(filterList, new MessageOutputStream(outputStream)); + + MessageInputStream inputStream = + MessageInputStream.MessageInputStreamFactory.createMessageInputStream(new ByteArrayInputStream( + outputStream.toByteArray()), + BulletinBoardMessage.class); + + return inputStream.asList(); + + } catch (Exception e) { + throw new CommunicationException(e.getCause() + " " + e.getMessage()); + } + + } + @Override public void readMessages(MessageFilterList filterList, MessageOutputStream out) throws CommunicationException { @@ -678,7 +738,7 @@ public class BulletinBoardSQLServer implements DeletableBulletinBoardServer{ } @Override - public IntMsg getMessageCount(MessageFilterList filterList) throws CommunicationException { + public Int32Value getMessageCount(MessageFilterList filterList) throws CommunicationException { BulletinBoardMessageList.Builder resultListBuilder = BulletinBoardMessageList.newBuilder(); @@ -697,57 +757,62 @@ public class BulletinBoardSQLServer implements DeletableBulletinBoardServer{ // Run query and stream the output using a MessageCallbackHandler List count = jdbcTemplate.query(sqlBuilder.toString(), sqlAndParameters.parameters, new LongMapper()); - return IntMsg.newBuilder().setValue(count.get(0).intValue()).build(); + return Int32Value.newBuilder().setValue(count.get(0).intValue()).build(); } - /** - * This method returns a string representation of the tag associated with a batch ID - * @param batchId is the given batch ID - * @return the String representation of the tag - */ - private String batchIdToTag(int batchId) { - return BATCH_ID_TAG_PREFIX + Integer.toString(batchId); - } - - /** * This method checks if a specified batch exists and is already closed - * @param signerId is the ID of the publisher of the batch - * @param batchId is the unique (per signer) batch ID + * @param msgID is the unique ID of the batch message * @return TRUE if the batch is closed and FALSE if it is still open or doesn't exist at all */ - private boolean isBatchClosed(ByteString signerId, int batchId) throws CommunicationException { + private boolean isBatchClosed(MessageID msgID) throws CommunicationException { MessageFilterList filterList = MessageFilterList.newBuilder() .addFilter(MessageFilter.newBuilder() - .setType(FilterType.SIGNER_ID) - .setId(signerId) - .build()) - .addFilter(MessageFilter.newBuilder() - .setType(FilterType.TAG) - .setTag(BATCH_TAG) - .build()) - .addFilter(MessageFilter.newBuilder() - .setType(FilterType.TAG) - .setTag(batchIdToTag(batchId)) + .setType(FilterType.MSG_ID) + .setId(msgID.getID()) .build()) .build(); - int count = getMessageCount(filterList).getValue(); - return count > 0; + List messages = readMessages(filterList); + + if (messages.size() <= 0){ + return false; + } + + return messages.get(0).getMsg().getIsStub(); } + /** + * This method checks if a specified batch exists and is still open + * @param signerId is the signer ID + * @param batchId is the batch ID + * @return TRUE if the batch is closed and FALSE if it is still open or doesn't exist at all + */ + private boolean isBatchOpen(ByteString signerId, int batchId) throws CommunicationException { + + String sql = sqlQueryProvider.getSQLString(QueryType.CHECK_BATCH_OPEN); + MapSqlParameterSource namedParameters = new MapSqlParameterSource(); + + namedParameters.addValue(QueryType.CHECK_BATCH_OPEN.getParamName(0),signerId.toByteArray()); + namedParameters.addValue(QueryType.CHECK_BATCH_OPEN.getParamName(1),batchId); + + List result = jdbcTemplate.query(sql, namedParameters, new LongMapper()); + + return (result.size() > 0 && result.get(0) > 0); + + } @Override - public BoolMsg beginBatch(BeginBatchMessage message) throws CommunicationException { + public BoolValue beginBatch(BeginBatchMessage message) throws CommunicationException { - // Check if batch is closed - if (isBatchClosed(message.getSignerId(), message.getBatchId())) { - return BoolMsg.newBuilder().setValue(false).build(); + // Check if batch is already open + if (isBatchOpen(message.getSignerId(), message.getBatchId())) { + return BoolValue.newBuilder().setValue(false).build(); } // Store tags @@ -760,17 +825,17 @@ public class BulletinBoardSQLServer implements DeletableBulletinBoardServer{ jdbcTemplate.update(sql,namedParameters); - return BoolMsg.newBuilder().setValue(true).build(); + return BoolValue.newBuilder().setValue(true).build(); } @Override - public BoolMsg postBatchMessage(BatchMessage batchMessage) throws CommunicationException{ + public BoolValue postBatchMessage(BatchMessage batchMessage) throws CommunicationException{ - // Check if batch is closed - if (isBatchClosed(batchMessage.getSignerId(), batchMessage.getBatchId())) { - return BoolMsg.newBuilder().setValue(false).build(); + // Make sure batch is open + if (!isBatchOpen(batchMessage.getSignerId(), batchMessage.getBatchId())) { + return BoolValue.newBuilder().setValue(false).build(); } // Add data @@ -784,19 +849,17 @@ public class BulletinBoardSQLServer implements DeletableBulletinBoardServer{ jdbcTemplate.update(sql, namedParameters); - return BoolMsg.newBuilder().setValue(true).build(); + return BoolValue.newBuilder().setValue(true).build(); } @Override - public BoolMsg closeBatchMessage(CloseBatchMessage message) throws CommunicationException { + public BoolValue closeBatch(CloseBatchMessage message) throws CommunicationException { - ByteString signerId = message.getSig().getSignerId(); + ByteString signerId = message.getSig(0).getSignerId(); int batchId = message.getBatchId(); - KeyHolder keyHolder = new GeneratedKeyHolder(); - // Check batch size String sql = sqlQueryProvider.getSQLString(QueryType.CHECK_BATCH_LENGTH); @@ -809,10 +872,10 @@ public class BulletinBoardSQLServer implements DeletableBulletinBoardServer{ List lengthResult = jdbcTemplate.query(sql, namedParameters, new LongMapper()); if (lengthResult.get(0) != message.getBatchLength()) { - return BoolMsg.newBuilder().setValue(false).build(); + return BoolValue.newBuilder().setValue(false).build(); } - // Get Tags and add them to CompleteBatch + // Get Tags and add them to BulletinBoardMessage sql = sqlQueryProvider.getSQLString(QueryType.GET_BATCH_TAGS); namedParameters = new MapSqlParameterSource(); @@ -823,73 +886,89 @@ public class BulletinBoardSQLServer implements DeletableBulletinBoardServer{ List beginBatchMessages = jdbcTemplate.query(sql, namedParameters, new BeginBatchMessageMapper()); if (beginBatchMessages == null || beginBatchMessages.size() <= 0 || beginBatchMessages.get(0) == null) { - return BoolMsg.newBuilder().setValue(false).build(); + return BoolValue.newBuilder().setValue(false).build(); } UnsignedBulletinBoardMessage unsignedMessage = UnsignedBulletinBoardMessage.newBuilder() .addAllTag(beginBatchMessages.get(0).getTagList()) - .addTag(BATCH_TAG) - .addTag(batchIdToTag(batchId)) .setTimestamp(message.getTimestamp()) + .setIsStub(true) .build(); - // Add actual batch data to CompleteBatch + // Digest the data - sql = sqlQueryProvider.getSQLString(QueryType.GET_BATCH_MESSAGE_DATA); + digest.reset(); + digest.update(unsignedMessage); + + sql = sqlQueryProvider.getSQLString(QueryType.GET_BATCH_MESSAGE_DATA_BY_IDS); namedParameters = new MapSqlParameterSource(); - namedParameters.addValue(QueryType.GET_BATCH_MESSAGE_DATA.getParamName(0),signerId.toByteArray()); - namedParameters.addValue(QueryType.GET_BATCH_MESSAGE_DATA.getParamName(1),batchId); - namedParameters.addValue(QueryType.GET_BATCH_MESSAGE_DATA.getParamName(2),0); // Read from the beginning + namedParameters.addValue(QueryType.GET_BATCH_MESSAGE_DATA_BY_IDS.getParamName(0),signerId.toByteArray()); + namedParameters.addValue(QueryType.GET_BATCH_MESSAGE_DATA_BY_IDS.getParamName(1),batchId); + namedParameters.addValue(QueryType.GET_BATCH_MESSAGE_DATA_BY_IDS.getParamName(2),0); // Read from the beginning + jdbcTemplate.query(sql, namedParameters, new BatchDataDigestHandler(digest)); - //TODO: Verification - // Will need to use the following to carry out the signature calculation: -// jdbcTemplate.query(sql, namedParameters, new BatchDataMapper()); + byte[] msgID = digest.digest(); + + //TODO: Signature verification // Create Bulletin Board message BulletinBoardMessage bulletinBoardMessage = BulletinBoardMessage.newBuilder() .setMsg(unsignedMessage) - .addSig(message.getSig()) + .addAllSig(message.getSigList()) .build(); - // Post message without checking signature validity - postMessage(bulletinBoardMessage, false); + // Post message with pre-calculated ID and without checking signature validity + long entryNum = postMessage(bulletinBoardMessage, msgID); + + // Remove signer ID and batch ID from batch data table + // This allows reuse of the batch ID in subsequent batches + + sql = sqlQueryProvider.getSQLString(QueryType.REMOVE_BATCH_IDS); + + namedParameters = new MapSqlParameterSource(); + + namedParameters.addValue(QueryType.REMOVE_BATCH_IDS.getParamName(0), signerId.toByteArray()); + namedParameters.addValue(QueryType.REMOVE_BATCH_IDS.getParamName(1), batchId); + + jdbcTemplate.update(sql, namedParameters); // Remove tags from temporary table sql = sqlQueryProvider.getSQLString(QueryType.REMOVE_BATCH_TAGS); - namedParameters = new MapSqlParameterSource(); - namedParameters.addValue(QueryType.REMOVE_BATCH_TAGS.getParamName(0), signerId.toByteArray()); - namedParameters.addValue(QueryType.REMOVE_BATCH_TAGS.getParamName(1), batchId); + jdbcTemplate.update(sql, namedParameters); + + // Add entry num to tag data table + sql = sqlQueryProvider.getSQLString(QueryType.ADD_ENTRY_NUM_TO_BATCH); + namedParameters.addValue(QueryType.ADD_ENTRY_NUM_TO_BATCH.getParamName(2), entryNum); jdbcTemplate.update(sql, namedParameters); // Return TRUE - return BoolMsg.newBuilder().setValue(true).build(); + return BoolValue.newBuilder().setValue(true).build(); + } @Override - public void readBatch(BatchSpecificationMessage message, MessageOutputStream out) throws CommunicationException, IllegalArgumentException{ + public void readBatch(BatchQuery batchQuery, MessageOutputStream out) throws CommunicationException, IllegalArgumentException{ // Check that batch is closed - if (!isBatchClosed(message.getSignerId(), message.getBatchId())) { + if (!isBatchClosed(batchQuery.getMsgID())) { throw new IllegalArgumentException("No such batch"); } String sql = sqlQueryProvider.getSQLString(QueryType.GET_BATCH_MESSAGE_DATA); MapSqlParameterSource namedParameters = new MapSqlParameterSource(); - namedParameters.addValue(QueryType.GET_BATCH_MESSAGE_DATA.getParamName(0),message.getSignerId().toByteArray()); - namedParameters.addValue(QueryType.GET_BATCH_MESSAGE_DATA.getParamName(1),message.getBatchId()); - namedParameters.addValue(QueryType.GET_BATCH_MESSAGE_DATA.getParamName(2),message.getStartPosition()); + namedParameters.addValue(QueryType.GET_BATCH_MESSAGE_DATA.getParamName(0),batchQuery.getMsgID().getID().toByteArray()); + namedParameters.addValue(QueryType.GET_BATCH_MESSAGE_DATA.getParamName(1),batchQuery.getStartPosition()); jdbcTemplate.query(sql, namedParameters, new BatchDataCallbackHandler(out)); } - /** * Finds the entry number of the last entry in the database * @return the entry number, or -1 if no entries are found diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/H2QueryProvider.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/H2QueryProvider.java index 8d97690..2f31e9e 100644 --- a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/H2QueryProvider.java +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/H2QueryProvider.java @@ -2,11 +2,7 @@ package meerkat.bulletinboard.sqlserver; import meerkat.protobuf.BulletinBoardAPI.FilterType; import org.apache.commons.dbcp2.BasicDataSource; -import org.h2.jdbcx.JdbcDataSource; -import javax.naming.Context; -import javax.naming.InitialContext; -import javax.naming.NamingException; import javax.sql.DataSource; import java.text.MessageFormat; import java.util.LinkedList; @@ -112,11 +108,20 @@ public class H2QueryProvider implements BulletinBoardSQLServer.SQLQueryProvider case GET_BATCH_MESSAGE_DATA: return MessageFormat.format( "SELECT Data FROM BatchTable" - + " WHERE SignerId = :{0} AND BatchId = :{1} AND SerialNum >= :{2}" - + " ORDER BY SerialNum ASC", + + " INNER JOIN MsgTable ON MsgTable.EntryNum = BatchTable.EntryNum" + + " WHERE MsgTable.MsgId = :{0} AND BatchTable.SerialNum >= :{1}" + + " ORDER BY BatchTable.SerialNum ASC", QueryType.GET_BATCH_MESSAGE_DATA.getParamName(0), - QueryType.GET_BATCH_MESSAGE_DATA.getParamName(1), - QueryType.GET_BATCH_MESSAGE_DATA.getParamName(2)); + QueryType.GET_BATCH_MESSAGE_DATA.getParamName(1)); + + case GET_BATCH_MESSAGE_DATA_BY_IDS: + return MessageFormat.format( + "SELECT Data FROM BatchTable" + + " WHERE SignerId = :{0} AND BatchId = :{1} AND SerialNum >= :{2}" + + " ORDER BY BatchTable.SerialNum ASC", + QueryType.GET_BATCH_MESSAGE_DATA_BY_IDS.getParamName(0), + QueryType.GET_BATCH_MESSAGE_DATA_BY_IDS.getParamName(1), + QueryType.GET_BATCH_MESSAGE_DATA_BY_IDS.getParamName(2)); case INSERT_BATCH_DATA: return MessageFormat.format( @@ -134,9 +139,16 @@ public class H2QueryProvider implements BulletinBoardSQLServer.SQLQueryProvider QueryType.CHECK_BATCH_LENGTH.getParamName(0), QueryType.CHECK_BATCH_LENGTH.getParamName(1)); + case CHECK_BATCH_OPEN: + return MessageFormat.format( + "SELECT COUNT(SignerId) AS signCount FROM BatchTagTable" + + " WHERE SignerId = :{0} AND BatchId = :{1}", + QueryType.CHECK_BATCH_OPEN.getParamName(0), + QueryType.CHECK_BATCH_OPEN.getParamName(1)); + case STORE_BATCH_TAGS: return MessageFormat.format( - "INSERT INTO BatchTagTable (SignerId, BatchId, TagId) VALUES (:{0}, :{1}, :{2})", + "INSERT INTO BatchTagTable (SignerId, BatchId, Tags) VALUES (:{0}, :{1}, :{2})", QueryType.STORE_BATCH_TAGS.getParamName(0), QueryType.STORE_BATCH_TAGS.getParamName(1), QueryType.STORE_BATCH_TAGS.getParamName(2)); @@ -153,6 +165,20 @@ public class H2QueryProvider implements BulletinBoardSQLServer.SQLQueryProvider QueryType.REMOVE_BATCH_TAGS.getParamName(0), QueryType.REMOVE_BATCH_TAGS.getParamName(1)); + case REMOVE_BATCH_IDS: + return MessageFormat.format( + "UPDATE BatchTable Set (SignerId, BatchId) = (NULL, NULL)" + + " WHERE SignerId = :{0} AND BatchId = :{1}", + QueryType.REMOVE_BATCH_IDS.getParamName(0), + QueryType.REMOVE_BATCH_IDS.getParamName(1)); + + case ADD_ENTRY_NUM_TO_BATCH: + return MessageFormat.format( + "UPDATE BatchTable SET EntryNum = :{2} WHERE SignerId = :{0} AND BatchId = :{1}", + QueryType.ADD_ENTRY_NUM_TO_BATCH.getParamName(0), + QueryType.ADD_ENTRY_NUM_TO_BATCH.getParamName(1), + QueryType.ADD_ENTRY_NUM_TO_BATCH.getParamName(2)); + default: throw new IllegalArgumentException("Cannot serve a query of type " + queryType); } @@ -255,12 +281,12 @@ public class H2QueryProvider implements BulletinBoardSQLServer.SQLQueryProvider list.add("CREATE INDEX IF NOT EXISTS SignerIndex ON SignatureTable(SignerId)"); list.add("CREATE UNIQUE INDEX IF NOT EXISTS SignatureIndex ON SignatureTable(SignerId, EntryNum)"); - list.add("CREATE TABLE IF NOT EXISTS BatchTable (SignerId TINYBLOB, BatchId INT, SerialNum INT, Data BLOB," + list.add("CREATE TABLE IF NOT EXISTS BatchTable (EntryNum INT, SignerId TINYBLOB, BatchId INT, SerialNum INT, Data BLOB," + " UNIQUE(SignerId, BatchId, SerialNum))"); + list.add("CREATE INDEX IF NOT EXISTS BatchDataIndex ON BatchTable(EntryNum, SerialNum)"); list.add("CREATE TABLE IF NOT EXISTS BatchTagTable (SignerId TINYBLOB, BatchId INT, Tags BLOB)"); - - list.add("CREATE UNIQUE INDEX IF NOT EXISTS BatchIndex ON BatchTagTable(SignerId, BatchId)"); + list.add("CREATE UNIQUE INDEX IF NOT EXISTS BatchTagIndex ON BatchTagTable(SignerId, BatchId)"); // This is used to create a simple table with one entry. // It is used for implementing a workaround for the missing INSERT IGNORE syntax @@ -275,13 +301,20 @@ public class H2QueryProvider implements BulletinBoardSQLServer.SQLQueryProvider List list = new LinkedList(); list.add("DROP TABLE IF EXISTS UtilityTable"); - list.add("DROP INDEX IF EXISTS BatchIndex"); + + list.add("DROP INDEX IF EXISTS BatchTagIndex"); list.add("DROP TABLE IF EXISTS BatchTagTable"); + + list.add("DROP INDEX IF EXISTS BatchDataIndex"); list.add("DROP TABLE IF EXISTS BatchTable"); - list.add("DROP INDEX IF EXISTS SignerIdIndex"); + list.add("DROP TABLE IF EXISTS MsgTagTable"); + + list.add("DROP INDEX IF EXISTS SignerIdIndex"); list.add("DROP TABLE IF EXISTS SignatureTable"); + list.add("DROP TABLE IF EXISTS TagTable"); + list.add("DROP TABLE IF EXISTS MsgTable"); return list; diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/MySQLQueryProvider.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/MySQLQueryProvider.java index ad3df68..2931a34 100644 --- a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/MySQLQueryProvider.java +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/MySQLQueryProvider.java @@ -111,11 +111,20 @@ public class MySQLQueryProvider implements SQLQueryProvider { case GET_BATCH_MESSAGE_DATA: return MessageFormat.format( "SELECT Data FROM BatchTable" - + " WHERE SignerId = :{0} AND BatchId = :{1} AND SerialNum >= :{2}" - + " ORDER BY SerialNum ASC", + + " INNER JOIN MsgTable ON MsgTable.EntryNum = BatchTable.EntryNum" + + " WHERE MsgTable.MsgId = :{0} AND BatchTable.SerialNum >= :{1}" + + " ORDER BY BatchTable.SerialNum ASC", QueryType.GET_BATCH_MESSAGE_DATA.getParamName(0), - QueryType.GET_BATCH_MESSAGE_DATA.getParamName(1), - QueryType.GET_BATCH_MESSAGE_DATA.getParamName(2)); + QueryType.GET_BATCH_MESSAGE_DATA.getParamName(1)); + + case GET_BATCH_MESSAGE_DATA_BY_IDS: + return MessageFormat.format( + "SELECT Data FROM BatchTable" + + " WHERE SignerId = :{0} AND BatchId = :{1} AND SerialNum >= :{2}" + + " ORDER BY BatchTable.SerialNum ASC", + QueryType.GET_BATCH_MESSAGE_DATA_BY_IDS.getParamName(0), + QueryType.GET_BATCH_MESSAGE_DATA_BY_IDS.getParamName(1), + QueryType.GET_BATCH_MESSAGE_DATA_BY_IDS.getParamName(2)); case INSERT_BATCH_DATA: return MessageFormat.format( @@ -133,18 +142,23 @@ public class MySQLQueryProvider implements SQLQueryProvider { QueryType.CHECK_BATCH_LENGTH.getParamName(0), QueryType.CHECK_BATCH_LENGTH.getParamName(1)); - case CONNECT_BATCH_TAG: + case CHECK_BATCH_OPEN: return MessageFormat.format( - "INSERT INTO BatchTagTable (SignerId, BatchId, TagId) SELECT :{0}, :{1}, TagId FROM TagTable" - + " WHERE Tag = :{2}", - QueryType.CONNECT_BATCH_TAG.getParamName(0), - QueryType.CONNECT_BATCH_TAG.getParamName(1), - QueryType.CONNECT_BATCH_TAG.getParamName(2)); + "SELECT COUNT(Tags) AS signCount FROM BatchTagTable" + + " WHERE SignerId = :{0} AND BatchId = :{1}", + QueryType.CHECK_BATCH_OPEN.getParamName(0), + QueryType.CHECK_BATCH_OPEN.getParamName(1)); + + case STORE_BATCH_TAGS: + return MessageFormat.format( + "INSERT INTO BatchTagTable (SignerId, BatchId, Tags) VALUES (:{0}, :{1}, :{2})", + QueryType.STORE_BATCH_TAGS.getParamName(0), + QueryType.STORE_BATCH_TAGS.getParamName(1), + QueryType.STORE_BATCH_TAGS.getParamName(2)); case GET_BATCH_TAGS: return MessageFormat.format( - "SELECT Tag FROM TagTable INNER JOIN BatchTagTable ON TagTable.TagId = BatchTagTable.TagId" - + " WHERE SignerId = :{0} AND BatchId = :{1} ORDER BY Tag ASC", + "SELECT Tags FROM BatchTagTable WHERE SignerId = :{0} AND BatchId = :{1}", QueryType.GET_BATCH_TAGS.getParamName(0), QueryType.GET_BATCH_TAGS.getParamName(1)); @@ -154,6 +168,20 @@ public class MySQLQueryProvider implements SQLQueryProvider { QueryType.REMOVE_BATCH_TAGS.getParamName(0), QueryType.REMOVE_BATCH_TAGS.getParamName(1)); + case REMOVE_BATCH_IDS: + return MessageFormat.format( + "UPDATE BatchTable Set SignerId = NULL, BatchId = NULL" + + " WHERE SignerId = :{0} AND BatchId = :{1}", + QueryType.REMOVE_BATCH_IDS.getParamName(0), + QueryType.REMOVE_BATCH_IDS.getParamName(1)); + + case ADD_ENTRY_NUM_TO_BATCH: + return MessageFormat.format( + "UPDATE BatchTable SET EntryNum = :{2} WHERE SignerId = :{0} AND BatchId = :{1}", + QueryType.ADD_ENTRY_NUM_TO_BATCH.getParamName(0), + QueryType.ADD_ENTRY_NUM_TO_BATCH.getParamName(1), + QueryType.ADD_ENTRY_NUM_TO_BATCH.getParamName(2)); + default: throw new IllegalArgumentException("Cannot serve a query of type " + queryType); } @@ -256,11 +284,11 @@ public class MySQLQueryProvider implements SQLQueryProvider { + " INDEX(SignerId(32)), CONSTRAINT Unique_Signature UNIQUE(SignerId(32), EntryNum)," + " CONSTRAINT FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum))"); - list.add("CREATE TABLE IF NOT EXISTS BatchTable (SignerId TINYBLOB, BatchId INT, SerialNum INT, Data BLOB," + list.add("CREATE TABLE IF NOT EXISTS BatchTable (EntryNum INT, SignerId TINYBLOB, BatchId INT, SerialNum INT, Data BLOB," + " CONSTRAINT Unique_Batch UNIQUE(SignerId(32), BatchId, SerialNum))"); - list.add("CREATE TABLE IF NOT EXISTS BatchTagTable (SignerId TINYBLOB, BatchId INT, TagId INT," - + " INDEX(SignerId(32), BatchId), CONSTRAINT FOREIGN KEY (TagId) REFERENCES TagTable(TagId))"); + list.add("CREATE TABLE IF NOT EXISTS BatchTagTable (SignerId TINYBLOB, BatchId INT, Tags BLOB," + + " INDEX(SignerId(32), BatchId))"); return list; } @@ -278,4 +306,4 @@ public class MySQLQueryProvider implements SQLQueryProvider { return list; } -} +} \ No newline at end of file diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/SQLiteQueryProvider.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/SQLiteQueryProvider.java index b64412d..0462543 100644 --- a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/SQLiteQueryProvider.java +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/SQLiteQueryProvider.java @@ -74,11 +74,20 @@ public class SQLiteQueryProvider implements BulletinBoardSQLServer.SQLQueryProvi case GET_BATCH_MESSAGE_DATA: return MessageFormat.format( "SELECT Data FROM BatchTable" - + " WHERE SignerId = :{0} AND BatchId = :{1} AND SerialNum >= :{2}" - + " ORDER BY SerialNum ASC", + + " INNER JOIN MsgTable ON MsgTable.EntryNum = BatchTable.EntryNum" + + " WHERE MsgTable.MsgId = :{0} AND BatchTable.SerialNum >= :{1}" + + " ORDER BY BatchTable.SerialNum ASC", QueryType.GET_BATCH_MESSAGE_DATA.getParamName(0), - QueryType.GET_BATCH_MESSAGE_DATA.getParamName(1), - QueryType.GET_BATCH_MESSAGE_DATA.getParamName(2)); + QueryType.GET_BATCH_MESSAGE_DATA.getParamName(1)); + + case GET_BATCH_MESSAGE_DATA_BY_IDS: + return MessageFormat.format( + "SELECT Data FROM BatchTable" + + " WHERE SignerId = :{0} AND BatchId = :{1} AND SerialNum >= :{2}" + + " ORDER BY BatchTable.SerialNum ASC", + QueryType.GET_BATCH_MESSAGE_DATA_BY_IDS.getParamName(0), + QueryType.GET_BATCH_MESSAGE_DATA_BY_IDS.getParamName(1), + QueryType.GET_BATCH_MESSAGE_DATA_BY_IDS.getParamName(2)); case INSERT_BATCH_DATA: return MessageFormat.format( @@ -96,13 +105,19 @@ public class SQLiteQueryProvider implements BulletinBoardSQLServer.SQLQueryProvi QueryType.CHECK_BATCH_LENGTH.getParamName(0), QueryType.CHECK_BATCH_LENGTH.getParamName(1)); - case CONNECT_BATCH_TAG: + case CHECK_BATCH_OPEN: return MessageFormat.format( - "INSERT INTO BatchTagTable (SignerId, BatchId, TagId) SELECT :{0}, :{1}, TagId FROM TagTable" - + " WHERE Tag = :{2}", - QueryType.CONNECT_BATCH_TAG.getParamName(0), - QueryType.CONNECT_BATCH_TAG.getParamName(1), - QueryType.CONNECT_BATCH_TAG.getParamName(2)); + "SELECT COUNT(Tags) AS signCount FROM BatchTagTable" + + " WHERE SignerId = :{0} AND BatchId = :{1}", + QueryType.CHECK_BATCH_OPEN.getParamName(0), + QueryType.CHECK_BATCH_OPEN.getParamName(1)); + + case STORE_BATCH_TAGS: + return MessageFormat.format( + "INSERT INTO BatchTagTable (SignerId, BatchId, Tags) VALUES (:{0}, :{1}, :{2})", + QueryType.STORE_BATCH_TAGS.getParamName(0), + QueryType.STORE_BATCH_TAGS.getParamName(1), + QueryType.STORE_BATCH_TAGS.getParamName(2)); case GET_BATCH_TAGS: return MessageFormat.format( @@ -117,6 +132,20 @@ public class SQLiteQueryProvider implements BulletinBoardSQLServer.SQLQueryProvi QueryType.REMOVE_BATCH_TAGS.getParamName(0), QueryType.REMOVE_BATCH_TAGS.getParamName(1)); + case REMOVE_BATCH_IDS: + return MessageFormat.format( + "UPDATE BatchTable Set (SignerId, BatchId) = (NULL, NULL)" + + " WHERE SignerId = :{0} AND BatchId = :{1}", + QueryType.REMOVE_BATCH_IDS.getParamName(0), + QueryType.REMOVE_BATCH_IDS.getParamName(1)); + + case ADD_ENTRY_NUM_TO_BATCH: + return MessageFormat.format( + "UPDATE BatchTable SET EntryNum = :{2} WHERE SignerId = :{0} AND BatchId = :{1}", + QueryType.ADD_ENTRY_NUM_TO_BATCH.getParamName(0), + QueryType.ADD_ENTRY_NUM_TO_BATCH.getParamName(1), + QueryType.ADD_ENTRY_NUM_TO_BATCH.getParamName(2)); + default: throw new IllegalArgumentException("Cannot serve a query of type " + queryType); } diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/BatchDataCallbackHandler.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/BatchDataCallbackHandler.java index 9ad0dc7..69d7bae 100644 --- a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/BatchDataCallbackHandler.java +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/BatchDataCallbackHandler.java @@ -1,10 +1,8 @@ package meerkat.bulletinboard.sqlserver.mappers; -import com.google.protobuf.InvalidProtocolBufferException; import meerkat.comm.MessageOutputStream; -import meerkat.protobuf.BulletinBoardAPI.BatchData; +import meerkat.protobuf.BulletinBoardAPI.BatchChunk; import org.springframework.jdbc.core.RowCallbackHandler; -import org.springframework.jdbc.core.RowMapper; import java.io.IOException; import java.sql.ResultSet; @@ -15,16 +13,16 @@ import java.sql.SQLException; */ public class BatchDataCallbackHandler implements RowCallbackHandler { - private final MessageOutputStream out; + private final MessageOutputStream out; - public BatchDataCallbackHandler(MessageOutputStream out) { + public BatchDataCallbackHandler(MessageOutputStream out) { this.out = out; } @Override public void processRow(ResultSet rs) throws SQLException { try { - out.writeMessage(BatchData.parseFrom(rs.getBytes(1))); + out.writeMessage(BatchChunk.parseFrom(rs.getBytes(1))); } catch (IOException e) { //TODO: Log } diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/BatchDataDigestHandler.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/BatchDataDigestHandler.java new file mode 100644 index 0000000..ae55d5c --- /dev/null +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/BatchDataDigestHandler.java @@ -0,0 +1,31 @@ +package meerkat.bulletinboard.sqlserver.mappers; + +import meerkat.bulletinboard.BulletinBoardDigest; +import meerkat.protobuf.BulletinBoardAPI.BatchChunk; +import org.springframework.jdbc.core.RowCallbackHandler; + +import java.io.IOException; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Created by Arbel Deutsch Peled on 19-Dec-15. + */ +public class BatchDataDigestHandler implements RowCallbackHandler { + + private final BulletinBoardDigest digest; + + public BatchDataDigestHandler(BulletinBoardDigest digest) { + this.digest = digest; + } + + @Override + public void processRow(ResultSet rs) throws SQLException { + try { + BatchChunk batchChunk = BatchChunk.parseFrom(rs.getBytes(1)); + digest.update(batchChunk.getData().toByteArray()); + } catch (IOException e) { + //TODO: Log + } + } +} diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/BatchDataMapper.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/BatchDataMapper.java deleted file mode 100644 index bc4ea26..0000000 --- a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/mappers/BatchDataMapper.java +++ /dev/null @@ -1,26 +0,0 @@ -package meerkat.bulletinboard.sqlserver.mappers; - -import com.google.protobuf.ByteString; -import com.google.protobuf.InvalidProtocolBufferException; -import meerkat.protobuf.BulletinBoardAPI.BatchData; -import org.springframework.jdbc.core.RowMapper; - -import java.sql.ResultSet; -import java.sql.SQLException; - -/** - * Created by Arbel Deutsch Peled on 19-Dec-15. - */ -public class BatchDataMapper implements RowMapper { - - @Override - public BatchData mapRow(ResultSet rs, int rowNum) throws SQLException { - - try { - return BatchData.parseFrom(rs.getBytes(1)); - } catch (InvalidProtocolBufferException e) { - return BatchData.getDefaultInstance(); - } - - } -} diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/webapp/BulletinBoardWebApp.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/webapp/BulletinBoardWebApp.java index 74b5f2a..7389090 100644 --- a/bulletin-board-server/src/main/java/meerkat/bulletinboard/webapp/BulletinBoardWebApp.java +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/webapp/BulletinBoardWebApp.java @@ -8,6 +8,8 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.StreamingOutput; +import com.google.protobuf.BoolValue; +import com.google.protobuf.Int32Value; import meerkat.bulletinboard.BulletinBoardServer; import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer; import meerkat.bulletinboard.sqlserver.H2QueryProvider; @@ -86,7 +88,7 @@ public class BulletinBoardWebApp implements BulletinBoardServer, ServletContextL @Consumes(MEDIATYPE_PROTOBUF) @Produces(MEDIATYPE_PROTOBUF) @Override - public BoolMsg postMessage(BulletinBoardMessage msg) throws CommunicationException { + public BoolValue postMessage(BulletinBoardMessage msg) throws CommunicationException { init(); return bulletinBoard.postMessage(msg); } @@ -102,7 +104,7 @@ public class BulletinBoardWebApp implements BulletinBoardServer, ServletContextL @Consumes(MEDIATYPE_PROTOBUF) @Produces(MEDIATYPE_PROTOBUF) @Override - public IntMsg getMessageCount(MessageFilterList filterList) throws CommunicationException { + public Int32Value getMessageCount(MessageFilterList filterList) throws CommunicationException { init(); return bulletinBoard.getMessageCount(filterList); } @@ -140,7 +142,7 @@ public class BulletinBoardWebApp implements BulletinBoardServer, ServletContextL @Consumes(MEDIATYPE_PROTOBUF) @Produces(MEDIATYPE_PROTOBUF) @Override - public BoolMsg beginBatch(BeginBatchMessage message) { + public BoolValue beginBatch(BeginBatchMessage message) { try { init(); return bulletinBoard.beginBatch(message); @@ -155,7 +157,7 @@ public class BulletinBoardWebApp implements BulletinBoardServer, ServletContextL @Consumes(MEDIATYPE_PROTOBUF) @Produces(MEDIATYPE_PROTOBUF) @Override - public BoolMsg postBatchMessage(BatchMessage batchMessage) { + public BoolValue postBatchMessage(BatchMessage batchMessage) { try { init(); return bulletinBoard.postBatchMessage(batchMessage); @@ -170,10 +172,10 @@ public class BulletinBoardWebApp implements BulletinBoardServer, ServletContextL @Consumes(MEDIATYPE_PROTOBUF) @Produces(MEDIATYPE_PROTOBUF) @Override - public BoolMsg closeBatchMessage(CloseBatchMessage message) { + public BoolValue closeBatch(CloseBatchMessage message) { try { init(); - return bulletinBoard.closeBatchMessage(message); + return bulletinBoard.closeBatch(message); } catch (CommunicationException e) { System.err.println(e.getMessage()); return null; @@ -182,10 +184,10 @@ public class BulletinBoardWebApp implements BulletinBoardServer, ServletContextL @Override - public void readBatch(BatchSpecificationMessage message, MessageOutputStream out) { + public void readBatch(BatchQuery batchQuery, MessageOutputStream out) throws CommunicationException, IllegalArgumentException { try { init(); - bulletinBoard.readBatch(message, out); + bulletinBoard.readBatch(batchQuery, out); } catch (CommunicationException | IllegalArgumentException e) { System.err.println(e.getMessage()); } @@ -212,17 +214,17 @@ public class BulletinBoardWebApp implements BulletinBoardServer, ServletContextL /** * Wrapper for the readBatch method which streams the output into the response */ - public StreamingOutput readBatch(final BatchSpecificationMessage message) { + public StreamingOutput readBatch(final BatchQuery batchQuery) { return new StreamingOutput() { @Override public void write(OutputStream output) throws IOException, WebApplicationException { - MessageOutputStream out = new MessageOutputStream<>(output); + MessageOutputStream out = new MessageOutputStream<>(output); try { init(); - bulletinBoard.readBatch(message, out); + bulletinBoard.readBatch(batchQuery, out); } catch (CommunicationException e) { //TODO: Log out.writeMessage(null); @@ -270,4 +272,4 @@ public class BulletinBoardWebApp implements BulletinBoardServer, ServletContextL close(); } -} +} \ No newline at end of file diff --git a/bulletin-board-server/src/test/java/meerkat/bulletinboard/BulletinBoardSQLServerIntegrationTest.java b/bulletin-board-server/src/test/java/meerkat/bulletinboard/BulletinBoardSQLServerIntegrationTest.java index 744a086..8d5e0c0 100644 --- a/bulletin-board-server/src/test/java/meerkat/bulletinboard/BulletinBoardSQLServerIntegrationTest.java +++ b/bulletin-board-server/src/test/java/meerkat/bulletinboard/BulletinBoardSQLServerIntegrationTest.java @@ -1,6 +1,7 @@ package meerkat.bulletinboard; +import com.google.protobuf.BoolValue; import com.google.protobuf.ByteString; import com.google.protobuf.TextFormat; @@ -8,6 +9,7 @@ import com.google.protobuf.Timestamp; import meerkat.comm.MessageInputStream; import meerkat.protobuf.Crypto.*; import meerkat.protobuf.BulletinBoardAPI.*; +import meerkat.protobuf.Comm.*; import static meerkat.bulletinboard.BulletinBoardConstants.*; import meerkat.rest.Constants; import meerkat.rest.ProtobufMessageBodyReader; @@ -20,7 +22,6 @@ import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Entity; import javax.ws.rs.client.WebTarget; -import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.io.InputStream; import java.util.List; @@ -61,7 +62,7 @@ public class BulletinBoardSQLServerIntegrationTest { WebTarget webTarget; Response response; - BoolMsg bool; + BoolValue bool; BulletinBoardMessage msg; @@ -95,7 +96,7 @@ public class BulletinBoardSQLServerIntegrationTest { response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(msg, Constants.MEDIATYPE_PROTOBUF)); System.err.println(response); - bool = response.readEntity(BoolMsg.class); + bool = response.readEntity(BoolValue.class); assert bool.getValue(); msg = BulletinBoardMessage.newBuilder() @@ -114,7 +115,7 @@ public class BulletinBoardSQLServerIntegrationTest { response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(msg, Constants.MEDIATYPE_PROTOBUF)); System.err.println(response); - bool = response.readEntity(BoolMsg.class); + bool = response.readEntity(BoolValue.class); assert bool.getValue(); // Test reading mechanism diff --git a/bulletin-board-server/src/test/java/meerkat/bulletinboard/GenericBulletinBoardServerTest.java b/bulletin-board-server/src/test/java/meerkat/bulletinboard/GenericBulletinBoardServerTest.java index affb1a3..a5997a3 100644 --- a/bulletin-board-server/src/test/java/meerkat/bulletinboard/GenericBulletinBoardServerTest.java +++ b/bulletin-board-server/src/test/java/meerkat/bulletinboard/GenericBulletinBoardServerTest.java @@ -17,6 +17,7 @@ import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.util.*; +import com.google.protobuf.BoolValue; import com.google.protobuf.ByteString; import com.google.protobuf.Timestamp; @@ -24,12 +25,12 @@ import meerkat.comm.CommunicationException; import meerkat.comm.MessageInputStream; import meerkat.comm.MessageOutputStream; import meerkat.comm.MessageInputStream.MessageInputStreamFactory; -import meerkat.crypto.Digest; import meerkat.crypto.concrete.ECDSASignature; import meerkat.crypto.concrete.SHA256Digest; import meerkat.protobuf.BulletinBoardAPI.*; +import meerkat.util.BulletinBoardMessageComparator; import meerkat.util.BulletinBoardMessageGenerator; -import org.h2.util.DateTimeUtils; +import meerkat.util.BulletinBoardUtils; import static org.junit.Assert.*; import static org.hamcrest.CoreMatchers.*; @@ -38,7 +39,7 @@ import static org.hamcrest.MatcherAssert.assertThat; public class GenericBulletinBoardServerTest { protected BulletinBoardServer bulletinBoardServer; - private GenericBatchDigitalSignature[] signers; + private GenericBulletinBoardSignature[] signers; private ByteString[] signerIDs; private Random random; @@ -58,24 +59,18 @@ public class GenericBulletinBoardServerTest { private String[] tags; private byte[][] data; - private List completeBatches; + private List batches; private final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); // Used to time the tests private BulletinBoardMessageGenerator bulletinBoardMessageGenerator; - private Digest digest; + private BulletinBoardDigest digest; + + private BulletinBoardMessageComparator comparator; /** * @param bulletinBoardServer is an initialized server. - * @throws InstantiationException - * @throws IllegalAccessException - * @throws CertificateException - * @throws KeyStoreException - * @throws NoSuchAlgorithmException - * @throws IOException - * @throws UnrecoverableKeyException - * @throws CommunicationException */ public void init(BulletinBoardServer bulletinBoardServer) { @@ -84,10 +79,10 @@ public class GenericBulletinBoardServerTest { this.bulletinBoardServer = bulletinBoardServer; - signers = new GenericBatchDigitalSignature[2]; + signers = new GenericBulletinBoardSignature[2]; signerIDs = new ByteString[signers.length]; - signers[0] = new GenericBatchDigitalSignature(new ECDSASignature()); - signers[1] = new GenericBatchDigitalSignature(new ECDSASignature()); + signers[0] = new GenericBulletinBoardSignature(new ECDSASignature()); + signers[1] = new GenericBulletinBoardSignature(new ECDSASignature()); InputStream keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE); char[] password = KEYFILE_PASSWORD1.toCharArray(); @@ -133,7 +128,9 @@ public class GenericBulletinBoardServerTest { random = new Random(0); bulletinBoardMessageGenerator = new BulletinBoardMessageGenerator(random); - digest = new SHA256Digest(); + digest = new GenericBulletinBoardDigest(new SHA256Digest()); + + comparator = new BulletinBoardMessageComparator(); long end = threadBean.getCurrentThreadCpuTime(); System.err.println("Finished initializing GenericBulletinBoardServerTest"); @@ -141,7 +138,7 @@ public class GenericBulletinBoardServerTest { // Initialize Batch variables - completeBatches = new ArrayList(10); + batches = new ArrayList<>(10); } @@ -445,73 +442,94 @@ public class GenericBulletinBoardServerTest { } - /** - * Tests that posting a message before opening a batch does not work - * @throws CommunicationException - */ - public void testBatchPostAfterClose() throws CommunicationException, SignatureException { - final int BATCH_ID = 100; + private void postAsBatch(BulletinBoardMessage message, ByteString signerId, int batchId, int chunkSize, boolean close) throws CommunicationException { - CompleteBatch completeBatch = new CompleteBatch(Timestamp.newBuilder() - .setSeconds(978325) - .setNanos(8097234) - .build()); - BoolMsg result; + List batchChunks = BulletinBoardUtils.breakToBatch(message, chunkSize); + BeginBatchMessage beginBatchMessage = BulletinBoardUtils.generateBeginBatchMessage(signerId, batchId, message); - // Create data - - completeBatch.setBeginBatchMessage(BeginBatchMessage.newBuilder() - .setSignerId(signerIDs[1]) - .setBatchId(BATCH_ID) - .addTag("Test") - .build()); - - BatchData batchData = BatchData.newBuilder() - .setData(ByteString.copyFrom((new byte[] {1,2,3,4}))) - .build(); - - completeBatch.appendBatchData(batchData); - - signers[1].updateContent(completeBatch); - - completeBatch.setSignature(signers[1].sign()); + BoolValue result; // Begin batch - result = bulletinBoardServer.beginBatch(completeBatch.getBeginBatchMessage()); + result = bulletinBoardServer.beginBatch(beginBatchMessage); assertThat("Was not able to open batch", result.getValue(), is(true)); - // Post data - - BatchMessage batchMessage = BatchMessage.newBuilder() - .setSignerId(signerIDs[1]) - .setBatchId(BATCH_ID) - .setData(batchData) - .build(); - - result = bulletinBoardServer.postBatchMessage(batchMessage); - - assertThat("Was not able to post batch message", result.getValue(), is(true)); - - // Close batch - - result = bulletinBoardServer.closeBatchMessage(completeBatch.getCloseBatchMessage()); - - assertThat("Was not able to close batch", result.getValue(), is(true)); - // Attempt to open batch again - result = bulletinBoardServer.beginBatch(completeBatch.getBeginBatchMessage()); + result = bulletinBoardServer.beginBatch(beginBatchMessage); assertThat("Was able to open a closed batch", result.getValue(), is(false)); - // Attempt to add batch data + // Post data - result = bulletinBoardServer.postBatchMessage(batchMessage); + BatchMessage batchMessage = BatchMessage.getDefaultInstance(); - assertThat("Was able to change a closed batch", result.getValue(), is(false)); + for (int i = 0 ; i < batchChunks.size() ; i++){ + + batchMessage = BatchMessage.newBuilder() + .setSignerId(signerId) + .setBatchId(batchId) + .setSerialNum(i) + .setData(batchChunks.get(i)) + .build(); + + result = bulletinBoardServer.postBatchMessage(batchMessage); + + assertThat("Was not able to post batch message", result.getValue(), is(true)); + + } + + // Close batch + if (close) { + + CloseBatchMessage closeBatchMessage = BulletinBoardUtils.generateCloseBatchMessage(batchId, batchChunks.size(), message); + + result = bulletinBoardServer.closeBatch(closeBatchMessage); + + assertThat("Was not able to close batch", result.getValue(), is(true)); + + } + + } + + /** + * Tests that opening the same batch ID while one is open results in failure + * @throws CommunicationException + */ + public void testReopen() throws CommunicationException, SignatureException { + + // Create data + final int BATCH_ID = 100; + final int DATA_SIZE = 1; + final int CHUNK_SIZE = 1; + final int TAG_NUMBER = 1; + + Timestamp timestamp = Timestamp.newBuilder() + .setSeconds(141510) + .setNanos(48015) + .build(); + + BulletinBoardMessage batch = bulletinBoardMessageGenerator.generateRandomMessage(signers, timestamp, DATA_SIZE, TAG_NUMBER); + + // Post batch but do not close + + postAsBatch(batch, signerIDs[0], BATCH_ID, CHUNK_SIZE, false); + + // Attempt to open batch again + + BoolValue result; + + result = bulletinBoardServer.beginBatch(BulletinBoardUtils.generateBeginBatchMessage(signerIDs[0], BATCH_ID, batch)); + + assertThat("Was able to open a closed batch", result.getValue(), is(false)); + + // Close batch + + result = bulletinBoardServer.closeBatch(BulletinBoardUtils.generateCloseBatchMessage(BATCH_ID, DATA_SIZE / CHUNK_SIZE, batch)); + + assertThat("Was not able to close batch", result.getValue(), is(true)); } @@ -521,110 +539,79 @@ public class GenericBulletinBoardServerTest { */ public void testPostBatch() throws CommunicationException, SignatureException { - CompleteBatch completeBatch = new CompleteBatch(Timestamp.newBuilder() - .setSeconds(12345) - .setNanos(1111) - .build()); - int currentBatch = completeBatches.size(); + // Create data + final int BATCH_ID = 200; + final int DATA_SIZE = 10000; + final int CHUNK_SIZE = 100; + final int TAG_NUMBER = 10; - BoolMsg result; + Timestamp timestamp = Timestamp.newBuilder() + .setSeconds(5235000) + .setNanos(32541) + .build(); - // Define batch data + BulletinBoardMessage batch = bulletinBoardMessageGenerator.generateRandomMessage(signers, timestamp, DATA_SIZE, TAG_NUMBER); - String[] tempBatchTags = new String[]{randomString(),randomString(),randomString()}; - byte[][] tempBatchData = new byte[Math.abs(randomByte())][]; + // Post batch - for (int i = 0 ; i < tempBatchData.length ; i++) { - - tempBatchData[i] = new byte[Math.abs(randomByte())]; - - for (int j = 0; j < tempBatchData[i].length; j++) { - tempBatchData[i][j] = randomByte(); - } - - } - - // Begin batch - - completeBatch.setBeginBatchMessage(BeginBatchMessage.newBuilder() - .setSignerId(signerIDs[0]) - .setBatchId(currentBatch) - .addAllTag(Arrays.asList(tempBatchTags)) - .build()); - - result = bulletinBoardServer.beginBatch(completeBatch.getBeginBatchMessage()); - - assertThat("Could not begin batch " + currentBatch, result.getValue(), is(true)); - - // Add batch data and randomize data posting order - - List dataOrder = new ArrayList(tempBatchData.length); - for (int i = 0 ; i < tempBatchData.length ; i++) { - dataOrder.add(i); - completeBatch.appendBatchData(BatchData.newBuilder() - .setData(ByteString.copyFrom(tempBatchData[i])) - .build()); - } - Collections.shuffle(dataOrder); - - // Post data - - for (int i = 0 ; i < tempBatchData.length ; i++) { - - int dataIndex = dataOrder.get(i); - - result = bulletinBoardServer.postBatchMessage(BatchMessage.newBuilder() - .setSignerId(signerIDs[0]) - .setBatchId(currentBatch) - .setSerialNum(dataIndex) - .setData(completeBatch.getBatchDataList().get(dataIndex)) - .build()); - - assertThat("Could not post batch data for batch ID " + currentBatch + " serial number " + dataIndex, - result.getValue(), is(true)); - - } - - // Close batch - - signers[0].updateContent(completeBatch); - completeBatch.setSignature(signers[0].sign()); - - result = bulletinBoardServer.closeBatchMessage(completeBatch.getCloseBatchMessage()); - - assertThat("Could not close batch " + currentBatch, result.getValue(), is(true)); + postAsBatch(batch, signerIDs[0], BATCH_ID, CHUNK_SIZE, true); // Update locally stored batches - completeBatches.add(completeBatch); + batches.add(batch); } public void testReadBatch() throws CommunicationException { - for (CompleteBatch completeBatch : completeBatches) { + for (BulletinBoardMessage message : batches) { try { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - BatchSpecificationMessage batchSpecificationMessage = - BatchSpecificationMessage.newBuilder() - .setSignerId(completeBatch.getBeginBatchMessage().getSignerId()) - .setBatchId(completeBatch.getBeginBatchMessage().getBatchId()) + digest.update(message); + + MessageID msgId = digest.digestAsMessageID(); + + MessageFilterList messageFilterList = MessageFilterList.newBuilder() + .addFilter(MessageFilter.newBuilder() + .setType(FilterType.MSG_ID) + .setId(msgId.getID()) + .build()) + .build(); + + bulletinBoardServer.readMessages(messageFilterList, new MessageOutputStream(outputStream)); + + MessageInputStream messageInputStream = + MessageInputStreamFactory.createMessageInputStream(new ByteArrayInputStream( + outputStream.toByteArray()), + BulletinBoardMessage.class); + + List messageList = messageInputStream.asList(); + + assertThat("No stub found for message ID " + msgId.getID().toStringUtf8(), messageList.size() == 1); + + BulletinBoardMessage stub = messageList.get(0); + + BatchQuery batchQuery = + BatchQuery.newBuilder() + .setMsgID(msgId) .setStartPosition(0) .build(); - bulletinBoardServer.readBatch(batchSpecificationMessage, new MessageOutputStream(outputStream)); + bulletinBoardServer.readBatch(batchQuery, new MessageOutputStream(outputStream)); - MessageInputStream inputStream = + MessageInputStream batchInputStream = MessageInputStreamFactory.createMessageInputStream(new ByteArrayInputStream( outputStream.toByteArray()), - BatchData.class); + BatchChunk.class); - List batchDataList = inputStream.asList(); + List batchChunkList = batchInputStream.asList(); - assertThat("Non-matching batch data for batch " + completeBatch.getBeginBatchMessage().getBatchId(), - completeBatch.getBatchDataList().equals(batchDataList), is(true)); + BulletinBoardMessage retrievedMessage = BulletinBoardUtils.gatherBatch(stub, batchChunkList); + + assertThat("Non-matching batch data for batch " + msgId.getID().toStringUtf8(), + comparator.compare(message, retrievedMessage) == 0); } catch (IOException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { assertThat("Error reading batch data list from input stream", false); @@ -646,7 +633,7 @@ public class GenericBulletinBoardServerTest { BulletinBoardMessage newMessage = bulletinBoardMessageGenerator.generateRandomMessage(signers, timestamp, 10, 10); - BoolMsg result = bulletinBoardServer.postMessage(newMessage); + BoolValue result = bulletinBoardServer.postMessage(newMessage); assertThat("Failed to post message to BB Server", result.getValue(), is(true)); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); diff --git a/bulletin-board-server/src/test/java/meerkat/bulletinboard/H2BulletinBoardServerTest.java b/bulletin-board-server/src/test/java/meerkat/bulletinboard/H2BulletinBoardServerTest.java index 512b4c9..4ee2282 100644 --- a/bulletin-board-server/src/test/java/meerkat/bulletinboard/H2BulletinBoardServerTest.java +++ b/bulletin-board-server/src/test/java/meerkat/bulletinboard/H2BulletinBoardServerTest.java @@ -7,7 +7,6 @@ import meerkat.comm.CommunicationException; import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.junit.runner.Result; import java.lang.management.ManagementFactory; import java.lang.management.ThreadMXBean; @@ -108,9 +107,9 @@ public class H2BulletinBoardServerTest { } @Test - public void testBatchPostAfterClose() { + public void testBatchReopen() { try{ - serverTest.testBatchPostAfterClose(); + serverTest.testReopen(); } catch (Exception e) { System.err.println(e.getMessage()); fail(e.getMessage()); diff --git a/bulletin-board-server/src/test/java/meerkat/bulletinboard/MySQLBulletinBoardServerTest.java b/bulletin-board-server/src/test/java/meerkat/bulletinboard/MySQLBulletinBoardServerTest.java index 273a53f..c4bd7fa 100644 --- a/bulletin-board-server/src/test/java/meerkat/bulletinboard/MySQLBulletinBoardServerTest.java +++ b/bulletin-board-server/src/test/java/meerkat/bulletinboard/MySQLBulletinBoardServerTest.java @@ -8,13 +8,9 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; -import java.io.IOException; import java.lang.management.ManagementFactory; import java.lang.management.ThreadMXBean; -import java.lang.reflect.InvocationTargetException; -import java.security.SignatureException; import java.sql.Connection; -import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.util.List; @@ -115,9 +111,9 @@ public class MySQLBulletinBoardServerTest { } @Test - public void testBatchPostAfterClose() { + public void testBatchReopen() { try{ - serverTest.testBatchPostAfterClose(); + serverTest.testReopen(); } catch (Exception e) { System.err.println(e.getMessage()); fail(e.getMessage()); diff --git a/distributed-key-generation/build.gradle b/distributed-key-generation/build.gradle new file mode 100644 index 0000000..c90a15a --- /dev/null +++ b/distributed-key-generation/build.gradle @@ -0,0 +1,223 @@ + +plugins { + id "us.kirchmeier.capsule" version "1.0.1" + id 'com.google.protobuf' version '0.7.0' +} + +apply plugin: 'java' +apply plugin: 'eclipse' +apply plugin: 'idea' + +apply plugin: 'maven-publish' + +// Uncomment the lines below to define an application +// (this will also allow you to build a "fatCapsule" which includes +// the entire application, including all dependencies in a single jar) +//apply plugin: 'application' +//mainClassName='your.main.ApplicationClass' + +// Is this a snapshot version? +ext { isSnapshot = false } + +ext { + groupId = 'org.factcenter.meerkat' + nexusRepository = "https://cs.idc.ac.il/nexus/content/groups/${isSnapshot ? 'unstable' : 'public'}/" + + // Credentials for IDC nexus repositories (needed only for using unstable repositories and publishing) + // Should be set in ${HOME}/.gradle/gradle.properties + nexusUser = project.hasProperty('nexusUser') ? project.property('nexusUser') : "" + nexusPassword = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : "" +} + +description = "TODO: Add a description" + +// Your project version +version = "0.0" + +version += "${isSnapshot ? '-SNAPSHOT' : ''}" + + +dependencies { + // Meerkat common + compile project(':meerkat-common') + + // Logging + compile 'org.slf4j:slf4j-api:1.7.7' + runtime 'ch.qos.logback:logback-classic:1.1.2' + runtime 'ch.qos.logback:logback-core:1.1.2' + + // Google protobufs + compile 'com.google.protobuf:protobuf-java:3.+' + + // Depend on test resources from meerkat-common + testCompile project(path: ':meerkat-common', configuration: 'testOutput') + + testCompile 'junit:junit:4.+' + + runtime 'org.codehaus.groovy:groovy:2.4.+' +} + + +/*==== You probably don't have to edit below this line =======*/ + +// Setup test configuration that can appear as a dependency in +// other subprojects +configurations { + testOutput.extendsFrom (testCompile) +} + +task testJar(type: Jar, dependsOn: testClasses) { + classifier = 'tests' + from sourceSets.test.output +} + +artifacts { + testOutput testJar +} + +// The run task added by the application plugin +// is also of type JavaExec. +tasks.withType(JavaExec) { + // Assign all Java system properties from + // the command line to the JavaExec task. + systemProperties System.properties +} + + +protobuf { + // Configure the protoc executable + protoc { + // Download from repositories + artifact = 'com.google.protobuf:protoc:3.+' + } +} + + +idea { + module { + project.sourceSets.each { sourceSet -> + + def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" + + println "Adding $srcDir" + // add protobuf generated sources to generated source dir. + if ("test".equals(sourceSet.name)) { + testSourceDirs += file(srcDir) + } else { + sourceDirs += file(srcDir) + } + generatedSourceDirs += file(srcDir) + + } + + // Don't exclude build directory + excludeDirs -= file(buildDir) + } +} + + +/*=================================== + * "Fat" Build targets + *===================================*/ + + +if (project.hasProperty('mainClassName') && (mainClassName != null)) { + + task mavenCapsule(type: MavenCapsule) { + description = "Generate a capsule jar that automatically downloads and caches dependencies when run." + applicationClass mainClassName + destinationDir = buildDir + } + + task fatCapsule(type: FatCapsule) { + description = "Generate a single capsule jar containing everything. Use -Pfatmain=... to override main class" + + destinationDir = buildDir + + def fatMain = hasProperty('fatmain') ? fatmain : mainClassName + + applicationClass fatMain + + def testJar = hasProperty('test') + + if (hasProperty('fatmain')) { + appendix = "fat-${fatMain}" + } else { + appendix = "fat" + } + + if (testJar) { + from sourceSets.test.output + } + } +} + + +/*=================================== + * Repositories + *===================================*/ + +repositories { + + // Prefer the local nexus repository (it may have 3rd party artifacts not found in mavenCentral) + maven { + url nexusRepository + + if (isSnapshot) { + credentials { username + password + + username nexusUser + password nexusPassword + } + } + } + + // Use local maven repository + mavenLocal() + + // Use 'maven central' for other dependencies. + mavenCentral() +} + +task "info" << { + println "Project: ${project.name}" +println "Description: ${project.description}" + println "--------------------------" + println "GroupId: $groupId" + println "Version: $version (${isSnapshot ? 'snapshot' : 'release'})" + println "" +} +info.description 'Print some information about project parameters' + + +/*=================================== + * Publishing + *===================================*/ + +publishing { + publications { + mavenJava(MavenPublication) { + groupId project.groupId + pom.withXml { + asNode().appendNode('description', project.description) + } + from project.components.java + + } + } + repositories { + maven { + url "https://cs.idc.ac.il/nexus/content/repositories/${project.isSnapshot ? 'snapshots' : 'releases'}" + credentials { username + password + + username nexusUser + password nexusPassword + } + } + } +} + + + diff --git a/distributed-key-generation/src/main/java/meerkat/crypto/dkg/comm/MessageHandler.java b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/comm/MessageHandler.java new file mode 100644 index 0000000..02c751b --- /dev/null +++ b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/comm/MessageHandler.java @@ -0,0 +1,47 @@ +package meerkat.crypto.dkg.comm; + +import com.google.protobuf.InvalidProtocolBufferException; +import meerkat.comm.Channel; +import meerkat.protobuf.Comm; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Created by Tzlil on 2/14/2016. + * + * an implementation of ReceiverCallback + */ +public abstract class MessageHandler implements Channel.ReceiverCallback { + final Logger logger = LoggerFactory.getLogger(getClass()); + + /** + * fixed value for broadcasting + */ + public static final int BROADCAST = 0; + + /** + * Handle a broadcast (or unicast) message. + * If the message is invalid, the handler can throw an {@link InvalidProtocolBufferException}, in which + * case the message will simply be ignored. + * @param envelope + */ + public abstract void handleMessage(Comm.BroadcastMessage envelope) throws InvalidProtocolBufferException; + + /** + * Was this broadcastMessage was received by broadcast channel + * @param broadcastMessage + * @return broadcastMessage user destination == BROADCAST + */ + public boolean isBroadcast(Comm.BroadcastMessage broadcastMessage){ + return broadcastMessage.getDestination() == BROADCAST; + } + + @Override + public void receiveMessage(Comm.BroadcastMessage envelope) { + try { + handleMessage(envelope); + } catch (InvalidProtocolBufferException e) { + logger.warn("Received invalid protocol buffer from channel", e); + } + } +} diff --git a/distributed-key-generation/src/main/java/meerkat/crypto/dkg/comm/MessageUtils.java b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/comm/MessageUtils.java new file mode 100644 index 0000000..a0cb79a --- /dev/null +++ b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/comm/MessageUtils.java @@ -0,0 +1,36 @@ +package meerkat.crypto.dkg.comm; + +import meerkat.protobuf.DKG; + +/** + * Created by talm on 12/04/16. + */ +public class MessageUtils { + public static DKG.Payload createMessage(DKG.Payload.Type type) { + return DKG.Payload.newBuilder().setType(type).build(); + } + + public static DKG.Payload createMessage(DKG.Payload.Type type, DKG.ShareMessage share) { + return DKG.Payload.newBuilder().setType(type).setShare(share).build(); + } + + public static DKG.Payload createMessage(DKG.Payload.Type type, DKG.ShareMessage.Builder share) { + return DKG.Payload.newBuilder().setType(type).setShare(share).build(); + } + + public static DKG.Payload createMessage(DKG.Payload.Type type, DKG.IDMessage id) { + return DKG.Payload.newBuilder().setType(type).setId(id).build(); + } + + public static DKG.Payload createMessage(DKG.Payload.Type type, DKG.IDMessage.Builder id) { + return DKG.Payload.newBuilder().setType(type).setId(id).build(); + } + + public static DKG.Payload createMessage(DKG.Payload.Type type, DKG.CommitmentMessage commitment) { + return DKG.Payload.newBuilder().setType(type).setCommitment(commitment).build(); + } + + public static DKG.Payload createMessage(DKG.Payload.Type type, DKG.CommitmentMessage.Builder commitment) { + return DKG.Payload.newBuilder().setType(type).setCommitment(commitment).build(); + } +} diff --git a/distributed-key-generation/src/main/java/meerkat/crypto/dkg/feldman/Party.java b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/feldman/Party.java new file mode 100644 index 0000000..1b2cbe8 --- /dev/null +++ b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/feldman/Party.java @@ -0,0 +1,40 @@ +package meerkat.crypto.dkg.feldman; + +import meerkat.crypto.secretsharing.shamir.Polynomial; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Created by Tzlil on 3/14/2016. + * + * contains all relevant information on specific party during + * the run of Joint Feldamn protocol + */ +public class Party { + public final int id; + public Polynomial.Point share; + public ArrayList commitments; + public boolean doneFlag; + public Protocol.ComplaintState[] complaints; + public boolean aborted; + + /** + * + * @param id party identifier - 1 <= id <= n + * @param n number of parties in current run protocol + * @param t protocol's threshold + */ + public Party(int id, int n, int t) { + this.id = id; + this.share = null; + this.doneFlag = false; + this.complaints = new Protocol.ComplaintState[n]; + Arrays.fill(this.complaints, Protocol.ComplaintState.OK); + this.commitments = new ArrayList(t + 1); + for (int i = 0; i <= t ; i++){ + commitments.add(null); + } + this.aborted = false; + } +} diff --git a/distributed-key-generation/src/main/java/meerkat/crypto/dkg/feldman/Protocol.java b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/feldman/Protocol.java new file mode 100644 index 0000000..d81c75a --- /dev/null +++ b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/feldman/Protocol.java @@ -0,0 +1,368 @@ +package meerkat.crypto.dkg.feldman; + +import meerkat.comm.Channel; +import meerkat.crypto.secretsharing.feldman.VerifiableSecretSharing; +import meerkat.crypto.secretsharing.shamir.Polynomial; +import com.google.protobuf.ByteString; +import meerkat.protobuf.DKG; +import org.factcenter.qilin.primitives.Group; +import org.factcenter.qilin.util.ByteEncoder; + +import java.math.BigInteger; +import java.util.*; + +import static meerkat.crypto.dkg.comm.MessageUtils.*; + +/** + * Created by Tzlil on 3/14/2016. + * + * an implementation of JointFeldman distributed key generation protocol. + * + * allows set of n parties to generate random key with threshold t. + */ +public class Protocol extends VerifiableSecretSharing { + public enum ComplaintState { + /** + * No complaints, no response required at this point. + */ + OK, + + /** + * Party received complaint, waiting for response from party + */ + Waiting, + + /** + * Party gave invalid answer to conplaint. + */ + Disqualified, + + /** + * Party received complaint, gave valid answer. + */ + NonDisqualified + } + + /** + * My share id. + */ + protected final int id; + + /** + * All parties participating in key generation. + * parties[id-1] has my info. + */ + private Party[] parties; + + /** + * communication object + */ + protected Channel channel; + + /** + * Encode/Decode group elements + */ + protected final ByteEncoder encoder; + + /** + * constructor + * @param q a large prime. + * @param t threshold. Any t+1 share holders can recover the secret, + * but any set of at most t share holders cannot + * @param n number of share holders + * @param zi secret, chosen from Zq + * @param random use for generate random polynomial + * @param group + * @param q a large prime dividing group order. + * @param g a generator of cyclic group of order q. + * the generated group is a subgroup of the given group. + * it must be chosen such that computing discrete logarithms is hard in this group. + * @param encoder Encode/Decode group elements (of type T) to/from byte array + */ + public Protocol(int t, int n, BigInteger zi, Random random, BigInteger q, T g + , Group group, int id, ByteEncoder encoder) { + super(t, n, zi, random, q, g,group); + this.id = id; + this.parties = new Party[n]; + for (int i = 1; i <= n ; i++){ + this.parties[i - 1] = new Party(i,n,t); + } + this.parties[id - 1].share = getShare(id); + this.encoder = encoder; + } + + /** + * setter + * @param channel + */ + public void setChannel(Channel channel){ + this.channel = channel; + } + + /** + * setter + * @param parties + */ + protected void setParties(Party[] parties){ + this.parties = parties; + } + + /** + * getter + * @return + */ + protected Party[] getParties(){ + return parties; + } + + /** + * stage1.1 according to the protocol + * Pi broadcasts Aik for k = 0,...,t. + */ + public void broadcastCommitments(){ + broadcastCommitments(commitmentsArrayList); + } + + /** + * pack commitments as messages and broadcast them + * @param commitments + */ + public void broadcastCommitments(ArrayList commitments){ + DKG.CommitmentMessage commitmentMessage; + for (int k = 0; k <= t ; k++){ + commitmentMessage = DKG.CommitmentMessage.newBuilder() + .setCommitment(ByteString.copyFrom(encoder.encode(commitments.get(k)))) + .setK(k) + .build(); + channel.broadcastMessage(createMessage(DKG.Payload.Type.COMMITMENT, commitmentMessage)); + } + } + + /** + * Send channel j her secret share (of my polynomial) + * @param j + */ + public void sendSecret(int j){ + ByteString secret = ByteString.copyFrom(getShare(j).y.toByteArray()); + channel.sendMessage(j, createMessage(DKG.Payload.Type.SHARE, + DKG.ShareMessage.newBuilder() + .setI(id) + .setJ(j) + .setShare(secret) + )); + } + + /** + * stage1.2 according to the protocol + * Pi computes the shares Sij for j = 1,...,n and sends Sij secretly to Pj. + */ + public void sendSecrets(){ + for (int j = 1; j <= n ; j++){ + if(j != id){ + sendSecret(j); + } + } + } + + /** + * + * @param i + * @return computeVerificationValue(j,parties[i - 1].commitments,group) == g ^ parties[i - 1].share mod q + */ + public boolean isValidShare(int i){ + Party party = parties[i - 1]; + synchronized (parties[i - 1]) { + return isValidShare(party.share, party.commitments, id); + } + } + + /** + * @param share + * @param commitments + * @param j + * @return computeVerificationValue(j,commitments,group) == g ^ secret.y mod q + */ + public boolean isValidShare(Polynomial.Point share, ArrayList commitments, int j){ + try{ + T v = computeVerificationValue(j,commitments,group); + return group.multiply(g,share.y).equals(v); + } + catch (NullPointerException e){ + return false; + } + } + + /** + * stage2 according to the protocol + * Pj verifies all the shares he received (using isValidShare) + * if check fails for an index i, Pj broadcasts a complaint against Pi. + */ + public void broadcastComplaints(){ + for (int i = 1; i <= n ; i++ ){ + if(i != id && !isValidShare(i)) { + broadcastComplaint(i); + } + } + } + + /** + * create a complaint message against i and broadcast it + * @param i + */ + private void broadcastComplaint(int i){ + //message = new Message(Type.Complaint, j) + DKG.IDMessage complaint = DKG.IDMessage.newBuilder() + .setId(i) + .build(); + channel.broadcastMessage(createMessage(DKG.Payload.Type.COMPLAINT, complaint)); + } + + /** + * create an answer message for j and broadcast it + * @param j + */ + public void broadcastComplaintAnswer(int j){ + channel.broadcastMessage(createMessage(DKG.Payload.Type.ANSWER, DKG.ShareMessage.newBuilder() + .setI(id) + .setJ(j) + .setShare(ByteString.copyFrom(getShare(j).y.toByteArray())))); + } + + /** + * stage3.1 according to the protocol + * if more than t players complain against a player Pi he is disqualified. + */ + public void answerAllComplainingPlayers(){ + ComplaintState[] complaints = parties[id - 1].complaints; + for (int i = 1; i <= n; i++) { + switch (complaints[i - 1]) { + case Waiting: + broadcastComplaintAnswer(i); + break; + default: + break; + } + } + + } + + /** + * stage3.2 according to the protocol + * if any of the revealed shares fails the verification test, player Pi is disqualified. + * set QUAL to be the set of non-disqualified players. + */ + public Set calcQUAL(){ + Set QUAL = new HashSet(); + boolean nonDisqualified; + int counter; + for (int i = 1; i <= n; i++) { + synchronized (parties[i - 1]) { + ComplaintState[] complaints = parties[i - 1].complaints; + nonDisqualified = true; + counter = 0; + for (int j = 1; j <= n; j++) { + switch (complaints[j - 1]) { + case OK: + break; + case NonDisqualified: + counter++; + break; + default: + nonDisqualified = false; + break; + } + if (!nonDisqualified) + break; + } + if (nonDisqualified && counter <= t) { + QUAL.add(i); + } + } + } + return QUAL; + } + + /** + * compute Y, the commitment to the final public key (includes only qualifying set) + * stage4.1 according to the protocol + * public value y is computed as y = multiplication of yi mod p for i in QUAL + */ + public T calcY(Set QUAL){ + T y = group.zero(); + for (int i : QUAL) { + synchronized (parties[i - 1]) { + y = group.add(y, parties[i - 1].commitments.get(0)); + } + } + return y; + } + + /** + * stage4.2 according to the protocol + * public verification values are computed as Ak = multiplication + * of Aik mod p for i in QUAL for k = 0,...,t + */ + public ArrayList calcCommitments(Set QUAL){ + ArrayList commitments = new ArrayList(t+1); + T value; + for (int k = 0; k <= t; k++){ + value = group.zero(); + for (int i : QUAL) { + synchronized (parties[i - 1]) { + value = group.add(value, parties[i - 1].commitments.get(k)); + } + } + commitments.add(k,value); + } + return commitments; + } + + /** + * stage4.3 according to the protocol + * Pj sets is share of the share as xj = sum of Sij mod q for i in QUAL + */ + public Polynomial.Point calcShare(Set QUAL){ + BigInteger xj = BigInteger.ZERO; + for (int i : QUAL) { + synchronized (parties[i - 1]) { + xj = xj.add(parties[i - 1].share.y); + } + } + return new Polynomial.Point(BigInteger.valueOf(id) , xj.mod(q)); + } + + /** + * decode commitment from arr + * @param arr + * @return + */ + public T decodeCommitment(byte[] arr){ + return encoder.decode(arr); + } + + /** + * getter + * @return id + */ + public int getId() { + return id; + } + + /** + * getter + * @return channel + */ + public Channel getChannel() { + return channel; + } + + + /** + * getter + * @return encoder + */ + public ByteEncoder getEncoder() { + return encoder; + } + +} diff --git a/distributed-key-generation/src/main/java/meerkat/crypto/dkg/feldman/User.java b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/feldman/User.java new file mode 100644 index 0000000..6563dfd --- /dev/null +++ b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/feldman/User.java @@ -0,0 +1,588 @@ +package meerkat.crypto.dkg.feldman; + +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.TextFormat; +import meerkat.comm.Channel; +import meerkat.crypto.dkg.comm.MessageHandler; +import meerkat.crypto.secretsharing.shamir.Polynomial; +import com.google.protobuf.ByteString; +import meerkat.protobuf.Comm; +import meerkat.protobuf.DKG; +import org.factcenter.qilin.primitives.Group; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Set; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingDeque; + +import static meerkat.crypto.dkg.comm.MessageUtils.createMessage; + +/** + * Created by Tzlil on 3/14/2016. + * + * implementation of joint feldman protocol user. + * + * according to the protocol, each user run feldman verifiable secret sharing + * as a dealer. + * + * by the end of run(), each party in QUAL has his own share of the generated random key. + * this key can be recover by any subset of QUAL of size at least t + 1. + */ +public class User implements Runnable { + final Logger logger = LoggerFactory.getLogger(getClass()); + + /** + * joint feldman protocol object + */ + protected final Protocol dkg; + + /** + * a generator of cyclic group of order q. + * the generated group is a subgroup of the given group. + * it must be chosen such that computing discrete logarithms is hard in this group. + */ + protected final T g; + + /** + * cyclic group contains g. + */ + protected final Group group; + + /** + * user id + */ + protected final int id; + + /** + * threshold + */ + protected final int t; + + /** + * number of shares + */ + protected final int n; + + + /** + * channel object + */ + protected final Channel channel; // + + /** + * All parties participating in key generation. + * parties[id-1] has my info. + */ + protected final Party[] parties; + + /** + * set of all non-disqualified parties + */ + protected Set QUAL; + + /** + * my own share of the generated random key. + */ + protected Polynomial.Point share; + + /** + * public verification values + */ + protected ArrayList commitments; + + /** + * public value, + * y = g ^ key + */ + protected T y; + + protected BlockingQueue receiveQueue; + + /** + * constructor + * @param dkg joint feldman protocol object + * @param channel channel object + */ + public User(Protocol dkg, Channel channel) { + this.dkg = dkg; + + this.g = dkg.getGenerator(); + this.group = dkg.getGroup(); + this.n = dkg.getN(); + this.t = dkg.getT(); + this.id = dkg.getId(); + + this.channel = channel; + dkg.setChannel(channel); + registerReceiverCallback(); + + this.parties = dkg.getParties(); + this.QUAL = null; + this.commitments = null; + this.share = null; + this.y = null; + + this.receiveQueue = new LinkedBlockingDeque<>(); + + } + + /** + * create MessageHandler and register it as ReceiverCallback + */ + protected void registerReceiverCallback() { + channel.registerReceiverCallback(new meerkat.crypto.dkg.comm.MessageHandler() { + @Override + public void handleMessage(Comm.BroadcastMessage envelope) throws InvalidProtocolBufferException { + receiveQueue.add(envelope); + } + }); + +// this.messageHandler = new MessageHandler(); +// channel.registerReceiverCallback(messageHandler); + } + + /** + * Wait for at least one message to arrive, then handle any messages currently in the queue + */ + protected void waitAndHandleReceivedMessages() { + Comm.BroadcastMessage msg = null; + while (!stop && msg == null) { + try { + msg = receiveQueue.take(); + } catch (InterruptedException e) { + // Possibly stop + } + } + while (!stop && msg != null) { + try { + handleMessage(msg); + } catch (InvalidProtocolBufferException e) { + logger.warn("Received invalid message: {}", TextFormat.printToString(msg)); + } + msg = receiveQueue.poll(); + } + } + + /** + * stage1 according to the protocol + * 1. Pi broadcasts Aik for k = 0,...,t. + * 2. Pi computes the shares Sij for j = 1,...,n and sends Sij secretly to Pj. + */ + protected void stage1() { + dkg.broadcastCommitments(); + dkg.sendSecrets(); + } + + /** + * Check if all shares and commitments have arrived from other parties + */ + protected boolean isStageOneCompleted() { + for (int i = 0 ; i < n ; i++) { + if (!parties[i].aborted) { + if (parties[i].share == null) + return false; + for (int k = 0 ; k <= t ; k++) { + if (parties[i].commitments.get(k) == null) + return false; + } + } + } + return true; + } + + protected void waitUntilStageOneCompleted() { + while (!stop && !isStageOneCompleted()) + waitAndHandleReceivedMessages(); + } + + protected boolean isStageTwoCompleted() { + for (int i = 0 ; i < n ; i++) { + if (!parties[i].aborted && !parties[i].doneFlag) + return false; + } + return true; + } + + /** + * stage2 according to the protocol + * Pj verifies all the shares he received + * if check fails for an index i, Pj broadcasts a complaint against Pi. + * Pj broadcasts done message at the end of this stage + */ + protected void stage2() { + dkg.broadcastComplaints(); + //broadcast done message after all complaints + channel.broadcastMessage(createMessage(DKG.Payload.Type.DONE)); + } + + /** + * wait until all other parties done complaining by receiving done message + */ + protected void waitUntilStageTwoCompleted(){ + while (!stop && !isStageTwoCompleted()) + waitAndHandleReceivedMessages(); + } + + + protected boolean haveReceivedAllStage3ComplaintAnswers() { + for (int i = 0; i < n; i++) { + if (parties[i].aborted) + continue; + for (int j = 0; j < n; j++) { + if (parties[i].complaints[j].equals(Protocol.ComplaintState.Waiting)) + return false; + + } + } + return true; + } + + /** + * stage3 according to the protocol + * 1. if more than t players complain against a player Pi he is disqualified. + * otherwise Pi broadcasts the share Sij for each complaining player Pj. + * 2. if any of the revealed shares fails the verification test, player Pi is disqualified. + * set QUAL to be the set of non-disqualified players. + */ + protected void stage3(){ + dkg.answerAllComplainingPlayers(); + + // wait until there is no complaint waiting for answer + while (!stop && !haveReceivedAllStage3ComplaintAnswers()) + waitAndHandleReceivedMessages(); + + this.QUAL = dkg.calcQUAL(); + } + + /** + * stage4 according to the protocol + * 1. public value y is computed as y = multiplication of yi mod p for i in QUAL + * 2. public verification values are computed as Ak = multiplication of Aik mod p for i in QUAL for k = 0,...,t + * 3. Pj sets is share of the secret as xj = sum of Sij mod q for i in QUAL + */ + protected void stage4(){ + this.y = dkg.calcY(QUAL); + this.commitments = dkg.calcCommitments(QUAL); + this.share = dkg.calcShare(QUAL); + } + + @Override + public void run() { + this.runThread = Thread.currentThread(); + // For debugging + String previousName = runThread.getName(); + runThread.setName(getClass().getName() +":" + getID()); + + try { + stage1(); + waitUntilStageOneCompleted(); + if (stop) return; + stage2(); + waitUntilStageTwoCompleted(); + if (stop) return; + stage3(); + if (stop) return; + stage4(); + } finally { + runThread.setName(previousName); + } + } + + /** + * current thread in the main loop + */ + protected Thread runThread; + + /** + * flag indicates if there was request to stop the current run of the protocol + */ + protected boolean stop = false; + + /** + * Request the current run loop to exit gracefully + */ + public void stop() { + try { + stop = true; + runThread.interrupt(); + }catch (Exception e){ + //do nothing + } + + } + + /** + * getter + * @return commitments + */ + public ArrayList getCommitments() { + return commitments; + } + + /** + * getter + * @return g + */ + public T getGenerator() { + return g; + } + + /** + * getter + * @return group + */ + public Group getGroup() { + return group; + } + + /** + * getter + * @return share + */ + public Polynomial.Point getShare() { + return share; + } + + /** + * getter + * @return id + */ + public int getID() { + return id; + } + + /** + * getter + * @return n + */ + public int getN() { + return n; + } + + /** + * getter + * @return t + */ + public int getT() { + return t; + } + + /** + * getter + * @return y + */ + public T getPublicValue() { + return y; + } + + /** + * getter + * @return QUAL + */ + public Set getQUAL() { + return QUAL; + } + + /** + * getter + * @return channel + */ + public Channel getChannel() { + return channel; + } + + /** + * commitment message is valid if: + * 1. it was received in broadcast chanel + * 2. the sender didn't sent this commitment before + */ + protected boolean isValidCommitmentMessage(int sender, boolean isBroadcast, DKG.CommitmentMessage commitmentMessage){ + int i = sender - 1; + int k = commitmentMessage.getK(); + return isBroadcast && parties[i].commitments.get(k) == null; + } + + /** + * secret message is valid if: + * 1. it was received in private chanel + * 2. the sender didn't sent secret message before + * 3. secret.i == i + * 4. secret.j == id + */ + protected boolean isValidSecretMessage(int sender, boolean isBroadcast, DKG.ShareMessage secretMessage){ + int i = secretMessage.getI(); + int j = secretMessage.getJ(); + if(sender != i || isBroadcast) + return false; + else + return parties[i - 1].share == null && j == id; + + } + + /** + * done message is valid if: + * 1. it was received in broadcast chanel + * 2. the sender didn't sent done message before + */ + protected boolean isValidDoneMessage(int sender, boolean isBroadcast){ + return isBroadcast && !parties[sender - 1].doneFlag; + } + + + /** + * complaint message is valid if: + * 1. it was received in broadcast chanel + * 2. the sender didn't complained against id before + */ + protected boolean isValidComplaintMessage(int sender, boolean isBroadcast, DKG.IDMessage complaintMessage){ + int i = sender; + int j = complaintMessage.getId(); + + assert(i > 0); + assert(j > 0); + assert(i <= parties.length); + assert(j <= parties[i-1].complaints.length); + + return isBroadcast && parties[i - 1].complaints[j - 1].equals( Protocol.ComplaintState.OK); + } + + /** + * answer message is valid if: + * 1. it was received in broadcast chanel + * 2. secret.i == i + * 3. 1 <= secret.j <= n + * 4. it is marked that j complained against i and i didn't received + */ + protected boolean isValidAnswerMessage(int sender, boolean isBroadcast, DKG.ShareMessage secretMessage){ + int i = secretMessage.getI(); + int j = secretMessage.getJ(); + if(sender != i || !isBroadcast) + return false; + else + return j >= 1 && j <= n && parties[i - 1].complaints[j - 1].equals(Protocol.ComplaintState.Waiting); + } + + + public void handleMessage(Comm.BroadcastMessage envelope) throws InvalidProtocolBufferException { + int sender = envelope.getSender(); + boolean isBroadcast = !envelope.getIsPrivate(); + DKG.Payload msg = DKG.Payload.parseFrom(envelope.getPayload()); + + logger.debug("handling Message: Dst={}, Src={}, [{}]", + envelope.getDestination(), envelope.getSender(), TextFormat.printToString(msg)); + + switch (msg.getType()) { + case COMMITMENT: + /** + * saves the commitment + */ + assert msg.getPayloadDataCase() == DKG.Payload.PayloadDataCase.COMMITMENT; + DKG.CommitmentMessage commitmentMessage = msg.getCommitment(); + if (isValidCommitmentMessage(sender, isBroadcast, commitmentMessage)) { + int i = sender - 1; + int k = commitmentMessage.getK(); + + parties[i].commitments.set(k, extractCommitment(commitmentMessage)); + } + break; + + + case SHARE: + /** + * saves the secret + */ + assert msg.getPayloadDataCase() == DKG.Payload.PayloadDataCase.SHARE; + DKG.ShareMessage secretMessage = msg.getShare(); + if(isValidSecretMessage(sender,isBroadcast,secretMessage)) { + int i = secretMessage.getI(); + Polynomial.Point secret = extractShare(id,secretMessage.getShare()); + parties[i - 1].share = secret; + } + break; + + case DONE: + + /** + * marks that the sender was finished sending all his complaints + */ + if(isValidDoneMessage(sender,isBroadcast)) { + parties[sender - 1].doneFlag = true; + } + break; + + case COMPLAINT: + /** + * marks that the sender was complained against id + */ + if (msg.getPayloadDataCase() != DKG.Payload.PayloadDataCase.ID) { + logger.error("User {} Expecting ID message, got from SRC={} msg {}", getID(), envelope.getSender(), TextFormat.printToString(msg)); + assert (msg.getPayloadDataCase() == DKG.Payload.PayloadDataCase.ID); + } + + DKG.IDMessage complaintMessage = msg.getId(); + if(isValidComplaintMessage(sender,isBroadcast,complaintMessage)){ + int i = sender; + int j = complaintMessage.getId(); + parties[j - 1].complaints[i - 1] = Protocol.ComplaintState.Waiting; + } + break; + case ANSWER: + /** + * if the secret is valid, marks the complaint as NonDisqualified + * else marks it as Disqualified + * in case that the complainer is id ( j == id ), saves the secret + */ + assert msg.getPayloadDataCase() == DKG.Payload.PayloadDataCase.SHARE; + secretMessage = msg.getShare(); + if(isValidAnswerMessage(sender,isBroadcast,secretMessage)) { + int i = secretMessage.getI(); + int j = secretMessage.getJ(); + Polynomial.Point secret = extractShare(j,secretMessage.getShare()); + if (dkg.isValidShare(secret, parties[i - 1].commitments, j)) { + parties[i - 1].complaints[j - 1] = Protocol.ComplaintState.NonDisqualified; + } else { + parties[i - 1].complaints[j - 1] = Protocol.ComplaintState.Disqualified; + } + if (j == id) { + parties[i - 1].share = secret; + } + } + break; + case ABORT: + /** + * marks that the sender was aborted + */ + parties[sender - 1].aborted = true; + break; + default: + logger.error("Bad message: SRC={}, DST={}, Payload={}", envelope.getSender(), envelope.getDestination(), TextFormat.printToString(msg)); + break; + + } + } + + /** + * extract share value from ByteString + * @param i + * @param share + * @return new Point (i,share) + */ + public Polynomial.Point extractShare(int i, ByteString share){ + BigInteger x = BigInteger.valueOf(i); + BigInteger y = new BigInteger(share.toByteArray()); + return new Polynomial.Point(x,y); + } + + /** + * + * @param commitmentMessage + * @return + */ + public T extractCommitment(DKG.CommitmentMessage commitmentMessage){ + return dkg.decodeCommitment(commitmentMessage.getCommitment().toByteArray()); + } +} diff --git a/distributed-key-generation/src/main/java/meerkat/crypto/dkg/gjkr/Party.java b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/gjkr/Party.java new file mode 100644 index 0000000..529b7be --- /dev/null +++ b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/gjkr/Party.java @@ -0,0 +1,28 @@ +package meerkat.crypto.dkg.gjkr; + +import meerkat.crypto.secretsharing.shamir.Polynomial; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +/** + * Created by Tzlil on 3/16/2016. + * + * an extension of DistributedKeyGenerationParty + * contains all relevant information on specific party during + * the run of the safe protocol + */ +public class Party extends meerkat.crypto.dkg.feldman.Party { + public Polynomial.Point shareT; + public boolean ysDoneFlag; + public ArrayList verifiableValues; + public Set recoverSharesSet; + public Party(int id, int n, int t) { + super(id, n, t); + this.shareT = null; + this.ysDoneFlag = false; + this.verifiableValues = new ArrayList(this.commitments); + this.recoverSharesSet = new HashSet(); + } +} diff --git a/distributed-key-generation/src/main/java/meerkat/crypto/dkg/gjkr/Protocol.java b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/gjkr/Protocol.java new file mode 100644 index 0000000..aad773c --- /dev/null +++ b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/gjkr/Protocol.java @@ -0,0 +1,165 @@ +package meerkat.crypto.dkg.gjkr; + +import meerkat.crypto.dkg.comm.MessageUtils; +import meerkat.crypto.secretsharing.feldman.VerifiableSecretSharing; +import meerkat.crypto.secretsharing.shamir.Polynomial; +import com.google.protobuf.ByteString; +import meerkat.protobuf.DKG; +import org.factcenter.qilin.primitives.Group; +import org.factcenter.qilin.util.ByteEncoder; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Random; +import java.util.Set; + +/** + * Created by Tzlil on 3/16/2016. + * TODO: comments + * TODO: put Channel (ChannelImpl) in constructor + */ +public class Protocol extends meerkat.crypto.dkg.feldman.Protocol { + + private VerifiableSecretSharing maskingShares; + private final T h; + private Party[] parties; + + public Protocol(int t, int n, BigInteger zi, Random random, BigInteger q, T g + , T h, Group group, int id, ByteEncoder byteEncoder) { + super(t, n, zi, random, q, g, group, id,byteEncoder); + this.h = h; + BigInteger r = new BigInteger(q.bitLength(),random).mod(q); + this.maskingShares = new VerifiableSecretSharing(t,n,r,random,q,h,group); + this.parties = new Party[n]; + for (int i = 1; i <= n ; i++){ + this.parties[i - 1] = new Party(i,n,t); + } + this.parties[id - 1].share = getShare(id); + this.parties[id - 1].shareT = maskingShares.getShare(id); + super.setParties(parties); + } + + protected Party[] getParties(){ + return parties; + } + + protected void setParties(Party[] parties) { + super.setParties(parties); + this.parties = parties; + } + + + @Override + public void sendSecret(int j) { + Polynomial.Point secret = getShare(j); + Polynomial.Point secretT = maskingShares.getShare(j); + DKG.ShareMessage doubleSecretMessage = createShareMessage(id,j,secret,secretT); + // TODO: Change SHARE to SHARE + channel.sendMessage(j, MessageUtils.createMessage(DKG.Payload.Type.SHARE, doubleSecretMessage)); + } + + + @Override + public boolean isValidShare(int i){ + Party party = parties[i - 1]; + return isValidShare(party.share, party.shareT, party.verifiableValues, id); + } + + /** + * test if share, shareT are valid with respect to verificationValues + * @param share + * @param shareT + * @param verificationValues + * @param j + * @return computeVerificationValue(j,verificationValues,group) == (g ^ share.y) * (h ^ shareT.y) mod q + */ + public boolean isValidShare(Polynomial.Point share, Polynomial.Point shareT, ArrayList verificationValues, int j){ + try { + T v = computeVerificationValue(j, verificationValues, group); + T exp = group.add(group.multiply(g, share.y), group.multiply(h, shareT.y)); + return exp.equals(v); + } + catch (NullPointerException e){ + return false; + } + } + + /** + * create complaint message against i and broadcast it + * @param share + * @param shareT + * @param i + */ + private void broadcastComplaint(Polynomial.Point share, Polynomial.Point shareT, int i){ + DKG.ShareMessage complaint = createShareMessage(i,id,share,shareT); + channel.broadcastMessage(MessageUtils.createMessage(DKG.Payload.Type.COMPLAINT, complaint)); + } + + /** + * stage4.3 according to the protocol + * if check fails for index i, Pj + */ + public void computeAndBroadcastComplaints(Set QUAL){ + Party party; + for (int i : QUAL) { + party = parties[i - 1]; + if (i != id) { + if (!super.isValidShare(party.share, party.commitments, id)) { + broadcastComplaint(party.share, party.shareT, i); + } + } + } + } + + /** + * compute verification values and broadcast them + * verificationValues[k] = g ^ commitments [k] * h ^ maskingShares.commitments [k] + */ + public void computeAndBroadcastVerificationValues(){ + ArrayList verificationValues = new ArrayList(t+1); + ArrayList hBaseCommitments = maskingShares.getCommitmentsArrayList(); + for (int k = 0 ; k <= t ; k++){ + verificationValues.add(k,group.add(commitmentsArrayList.get(k),hBaseCommitments.get(k))); + } + broadcastCommitments(verificationValues); + } + + /** + * pack share, shareT i,j to createShareMessage + * @param i + * @param j + * @param share + * @param shareT + * @return + */ + + private DKG.ShareMessage createShareMessage(int i, int j, Polynomial.Point share, Polynomial.Point shareT){ + DKG.ShareMessage ShareMessage = DKG.ShareMessage.newBuilder() + .setI(i) + .setJ(j) + .setShare(ByteString.copyFrom(share.y.toByteArray())) + .setShareT(ByteString.copyFrom(shareT.y.toByteArray())) + .build(); + return ShareMessage; + } + + @Override + public void broadcastComplaintAnswer(int j) { + DKG.ShareMessage answer = createShareMessage(id,j,getShare(j) + , maskingShares.getShare(j)); + channel.broadcastMessage(MessageUtils.createMessage(DKG.Payload.Type.ANSWER, answer)); + } + + public void broadcastAnswer(Polynomial.Point secret, Polynomial.Point secretT, int i){ + DKG.ShareMessage complaint = createShareMessage(i,id,secret,secretT); + channel.broadcastMessage(MessageUtils.createMessage(DKG.Payload.Type.ANSWER,complaint)); + } + + /** + * getter + * @return h + */ + public T getH() { + return h; + } +} diff --git a/distributed-key-generation/src/main/java/meerkat/crypto/dkg/gjkr/User.java b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/gjkr/User.java new file mode 100644 index 0000000..5c1b418 --- /dev/null +++ b/distributed-key-generation/src/main/java/meerkat/crypto/dkg/gjkr/User.java @@ -0,0 +1,359 @@ +package meerkat.crypto.dkg.gjkr; + +import com.google.protobuf.InvalidProtocolBufferException; +import meerkat.crypto.utils.Arithmetic; +import meerkat.crypto.utils.concrete.Fp; +import meerkat.comm.Channel; +import meerkat.crypto.secretsharing.shamir.Polynomial; +import meerkat.crypto.secretsharing.shamir.SecretSharing; +import meerkat.protobuf.Comm; +import meerkat.protobuf.DKG; + +import java.math.BigInteger; +import java.util.ArrayList; + +import static meerkat.crypto.dkg.comm.MessageUtils.*; + +/** + * Created by Tzlil on 3/16/2016. + *

+ * implementation of gjkr protocol user. + *

+ * this protocol extends joint Feldman protocol by splitting the protocol to commitment stage (stages 1,2,3) + * and revealing stage (stage 4). + *

+ * as in joint Feldman, each party in QUAL has his own share of the generated random key. + * this key can be recover by any subset of QUAL of size at least t + 1. + */ +public class User extends meerkat.crypto.dkg.feldman.User { + + /** + * All parties participating in key generation. + * parties[id-1] has my info. + */ + protected Party[] parties; + + /** + * gjkr secure protocol object + */ + protected final Protocol sdkg; + + boolean isStage4; + + /** + * constructor + * + * @param sdkg gjkr protocol object + * @param channel channel object + */ + public User(Protocol sdkg, Channel channel) { + super(sdkg, channel); + this.sdkg = sdkg; + this.parties = sdkg.getParties(); + } + + /** + * stage1 according to the protocol + * 1. Pi broadcasts Cik=Aik*Bik for k = 0,...,t. + * 2. Pi computes the shares Sij,Sij' for j = 1,...,n and sends Sij,Sij' secretly to Pj. + */ + @Override + protected void stage1() { + sdkg.computeAndBroadcastVerificationValues(); + sdkg.sendSecrets(); + } + + + + @Override + protected void waitUntilStageOneCompleted() { + super.waitUntilStageOneCompleted(); + // save the received commitments as verification values + ArrayList temp; + for (int i = 0; i < n; i++) { + temp = parties[i].verifiableValues; + parties[i].verifiableValues = parties[i].commitments; + parties[i].commitments = temp; + } + } + + /** + * stage2 according to the protocol + * Pj verifies all the shares,sharesT he received + * if check fails for an index i, Pj broadcasts a complaint against Pi. + * Pj broadcasts done message at the end of this stage + */ + @Override + protected void stage2() { + sdkg.broadcastComplaints(); + //broadcast done message after all complaints + channel.broadcastMessage(createMessage(DKG.Payload.Type.DONE)); + } + + /** + * Check if all non-aborting qualified parties have sent commitments. + * @return + */ + protected boolean haveAllQualPartiesCommitted() { + for (int i : QUAL) { + if (parties[i - 1].aborted) + continue; + for (int k = 0; k <= t; k++) { + if (parties[i - 1].commitments.get(k) == null) + return false; + } + } + return true; + } + + + /** + * Check if all non-aborting qualified parties sent a done message + * @return + */ + protected boolean areAllQualPartiesDone() { + for (int i : QUAL) { + if (parties[i - 1].aborted) + continue; + for (int k = 0; k <= t; k++) { + if (!parties[i - 1].ysDoneFlag) + return false; + } + } + return true; + } + + /** + * Check if at least t + 1 secrets were received foreach i in QUAL that aborted + * @return + */ + protected boolean haveReceivedEnoughSecretShares() { + for (int i : QUAL) { + if (parties[i - 1].aborted && parties[i - 1].recoverSharesSet.size() <= t) + return false; + } + return true; + } + + /** + * broadcast commitments and recover parties information if necessary + */ + private void resolveQualifyingPublicKey() { + sdkg.broadcastCommitments(); + // wait until all parties in QUAL broadcast their commitments or aborted + while (!stop && !haveAllQualPartiesCommitted()) + waitAndHandleReceivedMessages(); + + if (stop) + return; + + sdkg.computeAndBroadcastComplaints(QUAL); + + //broadcast done message after all complaints + channel.broadcastMessage(createMessage(DKG.Payload.Type.DONE)); + + // wait until all parties in QUAL done or aborted + while (!stop && !areAllQualPartiesDone()) + waitAndHandleReceivedMessages(); + + if (stop) + return; + + // broadcast i private secret foreach i in QUAL that aborted + for (int i : QUAL) { + if (parties[i - 1].aborted) { + sdkg.broadcastAnswer(parties[i - 1].share, parties[i - 1].shareT, i); + } + } + // wait until at least t + 1 secrets will received foreach i in QUAL that aborted + while (!stop && !haveReceivedEnoughSecretShares()) + waitAndHandleReceivedMessages(); + + if (stop) + return; + + Arithmetic arithmetic = new Fp(sdkg.getQ()); + // restore necessary information + for (int i = 0; i < n; i++) { + if (parties[i].recoverSharesSet.isEmpty()) { + continue; + } + Polynomial.Point[] shares = new Polynomial.Point[t + 1]; + int j = 0; + for (Polynomial.Point share : parties[i].recoverSharesSet) { + shares[j++] = share; + if (j >= shares.length) { + break; + } + } + Polynomial polynomial = SecretSharing.recoverPolynomial(shares, arithmetic); + BigInteger[] coefficients = polynomial.getCoefficients(); + for (int k = 0; k <= t; k++) { + parties[i].commitments.add(k, group.multiply(g, coefficients[k])); + } + parties[i].share = new Polynomial.Point(BigInteger.valueOf(id), polynomial); + } + } + + /** + * notifies message handler and message handler that stage 4 was started + */ + protected void setStage4() { + isStage4 = true; + } + + @Override + protected void stage4() { + setStage4(); + resolveQualifyingPublicKey(); + if (stop) return; + super.stage4(); + } + + + /** + * if !isStage4 as super, with extension to double secret message + * else answer message is valid if: + * 1. it was received in broadcast chanel + * 2. secret.j == sender + * 3. QUAL contains i and j + */ + protected boolean isValidAnswerMessage(int sender, boolean isBroadcast, DKG.ShareMessage doubleSecretMessage) { + if (!isStage4) { + return super.isValidAnswerMessage(sender, isBroadcast, doubleSecretMessage); + } else { + int i = doubleSecretMessage.getI(); + int j = doubleSecretMessage.getJ(); + return isBroadcast && j == sender && parties[i - 1].aborted && !parties[j - 1].aborted + && QUAL.contains(i) && QUAL.contains(j); + } + } + + + /** + * as in super with respect to protocol stage + */ + @Override + protected boolean isValidDoneMessage(int sender, boolean isBroadcast) { + if (!isStage4) { + return super.isValidDoneMessage(sender, isBroadcast); + } else { + return isBroadcast && !parties[sender - 1].ysDoneFlag; + } + } + + /** + * use only in stage4 + * complaint message is valid if: + * 1. it was received in broadcast chanel + * 2. secret.j == sender + * 3. QUAL contains i and j + */ + protected boolean isValidComplaintMessage(int sender, boolean isBroadcast, + DKG.ShareMessage complaintMessage) { + int i = complaintMessage.getI(); + int j = complaintMessage.getJ(); + return isBroadcast && j == sender && QUAL.contains(i) && QUAL.contains(j); + } + + + @Override + public void handleMessage(Comm.BroadcastMessage envelope) throws InvalidProtocolBufferException { + int sender = envelope.getSender(); + boolean isBroadcast = !envelope.getIsPrivate(); + DKG.Payload msg = DKG.Payload.parseFrom(envelope.getPayload()); + switch (msg.getType()) { + case SHARE: + /** + * as in super, with extension to double secret message + */ + DKG.ShareMessage doubleSecretMessage = msg.getShare(); + if (isValidSecretMessage(sender, isBroadcast, doubleSecretMessage)) { + int i = doubleSecretMessage.getI(); + synchronized (parties[i - 1]) { + parties[i - 1].share = extractShare(id, doubleSecretMessage.getShare()); + parties[i - 1].shareT = extractShare(id, doubleSecretMessage.getShareT()); + parties[i - 1].notify(); + } + } + break; + case ANSWER: + /** + * if !isStage4 as super, with extension to double secret message + * else saves secret + */ + assert msg.getPayloadDataCase() == DKG.Payload.PayloadDataCase.SHARE; + doubleSecretMessage = msg.getShare(); + if (isValidAnswerMessage(sender, isBroadcast, doubleSecretMessage)) { + int i = doubleSecretMessage.getI(); + int j = doubleSecretMessage.getJ(); + Polynomial.Point secret = extractShare(j, doubleSecretMessage.getShare()); + Polynomial.Point secretT = extractShare(j, doubleSecretMessage.getShareT()); + synchronized (parties[i - 1]) { + if (!isStage4) { + if (sdkg.isValidShare(secret, secretT, parties[j - 1].verifiableValues, i)) { + parties[i - 1].complaints[j - 1] = meerkat.crypto.dkg.feldman.Protocol.ComplaintState.NonDisqualified; + + } else { + parties[i - 1].complaints[j - 1] = meerkat.crypto.dkg.feldman.Protocol.ComplaintState.Disqualified; + } + if (j == id) { + parties[i - 1].share = secret; + parties[i - 1].shareT = secretT; + } + } else if (sdkg.isValidShare(secret, secretT, parties[i - 1].verifiableValues, j)) { + parties[i - 1].recoverSharesSet.add(secret); + } + parties[i - 1].notify(); + } + } + break; + case DONE: + /** + * as in super with respect to protocol state + */ + if (!isStage4) + super.handleMessage(envelope); + else { + if (isValidDoneMessage(sender, isBroadcast)) { + synchronized (parties[sender - 1]) { + parties[sender - 1].ysDoneFlag = true; + parties[sender - 1].notify(); + } + } + } + break; + case COMPLAINT: + /** + * if !isStage4 as in super + * else if secret,secretT are valid with respect to verifiableValues but + * secret is not valid with respect to commitments then + * marks i as aborted + */ + if (!isStage4) { + super.handleMessage(envelope); + } else { + assert (msg.getPayloadDataCase() == DKG.Payload.PayloadDataCase.SHARE); + DKG.ShareMessage ysComplaintMessage = msg.getShare(); + if (isValidComplaintMessage(sender, isBroadcast, ysComplaintMessage)) { + int i = ysComplaintMessage.getI(); + int j = ysComplaintMessage.getJ(); + Polynomial.Point secret = extractShare(i, ysComplaintMessage.getShare()); + Polynomial.Point secretT = extractShare(i, ysComplaintMessage.getShareT()); + if (sdkg.isValidShare(secret, secretT, parties[i - 1].verifiableValues, j) + && !dkg.isValidShare(secret, parties[i - 1].commitments, j)) { + synchronized (parties[i - 1]) { + parties[i - 1].aborted = true; + parties[i - 1].notify(); + } + } + } + } + break; + default: + super.handleMessage(envelope); + break; + } + } + +} \ No newline at end of file diff --git a/distributed-key-generation/src/main/java/meerkat/crypto/secretsharing/feldman/VerifiableSecretSharing.java b/distributed-key-generation/src/main/java/meerkat/crypto/secretsharing/feldman/VerifiableSecretSharing.java new file mode 100644 index 0000000..0c91431 --- /dev/null +++ b/distributed-key-generation/src/main/java/meerkat/crypto/secretsharing/feldman/VerifiableSecretSharing.java @@ -0,0 +1,117 @@ +package meerkat.crypto.secretsharing.feldman; + +import meerkat.crypto.secretsharing.shamir.Polynomial; +import meerkat.crypto.secretsharing.shamir.SecretSharing; +import org.factcenter.qilin.primitives.Group; + +import java.util.ArrayList; +import java.math.BigInteger; +import java.util.Random; + +/** + * Created by Tzlil on 1/27/2016. + * + * an implementation of Feldman's verifiable secret sharing scheme. + * + * allows trusted dealer to share a key x among n parties. + * + */ +public class VerifiableSecretSharing extends SecretSharing { + /** + * cyclic group contains g. + */ + protected final Group group; + /** + * a generator of cyclic group of order q. + * the generated group is a subgroup of the given group. + * it must be chosen such that computing discrete logarithms is hard in this group. + */ + protected final T g; + /** + * commitments to polynomial coefficients. + * commitments[k] = g ^ coefficients[k] (group operation) + */ + protected final ArrayList commitmentsArrayList; + + /** + * constructor + * @param q a large prime. + * @param t threshold. Any t+1 share holders can recover the secret, + * but any set of at most t share holders cannot + * @param n number of share holders + * @param zi secret, chosen from Zq + * @param random use for generate random polynomial + * @param group + * @param q a large prime dividing group order. + * @param g a generator of cyclic group of order q. + * the generated group is a subgroup of the given group. + * it must be chosen such that computing discrete logarithms is hard in this group. + */ + public VerifiableSecretSharing(int t, int n, BigInteger zi, Random random, BigInteger q, T g + , Group group) { + super(t, n, zi, random,q); + this.g = g; + this.group = group; + assert (this.group.contains(g)); + this.commitmentsArrayList = generateCommitments(); + } + + /** + * commitments[i] = g ^ polynomial.coefficients[i] + * @return commitments + */ + private ArrayList generateCommitments() { + + Polynomial polynomial = getPolynomial(); + BigInteger[] coefficients = polynomial.getCoefficients(); + ArrayList commitments = new ArrayList(t + 1); + for (int i = 0 ; i <= t;i++){ + commitments.add(i,group.multiply(g,coefficients[i])); + } + return commitments; + } + + /** + * Compute verification value (g^{share value}) using coefficient commitments sent by dealer and my share id. + * @param j my share holder id + * @param commitments commitments to polynomial coefficients of share (received from dealer) + * @param group + * + * @return product of Aik ^ (j ^ k) == g ^ polynomial(i) + */ + public static T computeVerificationValue(int j, ArrayList commitments, Group group) { + T v = group.zero(); + BigInteger power = BigInteger.ONE; + BigInteger J = BigInteger.valueOf(j); + for (int k = 0 ; k < commitments.size() ; k ++){ + v = group.add(v,group.multiply(commitments.get(k),power)); + power = power.multiply(J); + } + return v; + } + + /** + * getter + * @return generator of group + */ + public T getGenerator() { + return g; + } + + /** + * getter + * @return group + */ + public Group getGroup(){ + return group; + } + + /** + * getter + * @return commitmentsArrayList + */ + public ArrayList getCommitmentsArrayList() { + return commitmentsArrayList; + } + +} diff --git a/distributed-key-generation/src/main/java/meerkat/crypto/secretsharing/shamir/LagrangePolynomial.java b/distributed-key-generation/src/main/java/meerkat/crypto/secretsharing/shamir/LagrangePolynomial.java new file mode 100644 index 0000000..e12d65f --- /dev/null +++ b/distributed-key-generation/src/main/java/meerkat/crypto/secretsharing/shamir/LagrangePolynomial.java @@ -0,0 +1,66 @@ +package meerkat.crypto.secretsharing.shamir; + +import meerkat.crypto.utils.Arithmetic; + +import java.math.BigInteger; + +/** + * Created by Tzlil on 1/28/2016. + * + * container of lagrange polynomial + * + * Constructor is private (use {@link #lagrangePolynomials(Polynomial.Point[], Arithmetic)} to construct) + * + * l = (evaluate/divisor)* polynomial + * + * Note : image and divisor stored separately for avoiding lose of information by division + */ +class LagrangePolynomial{ + public final Polynomial polynomial; + public final BigInteger image; + public final BigInteger divisor; + + /** + * inner constructor, stores all given parameters + * @param polynomial + * @param image + * @param divisor + */ + private LagrangePolynomial(Polynomial polynomial, BigInteger image, BigInteger divisor) { + this.polynomial = polynomial; + this.image = image; + this.divisor = divisor; + } + + /** + * static method + * @param points array points s.t there are no couple of points that shares the same x value + * + * @return the lagrange polynomials that mach to given points. + * in case there exists i != j s.t points[i].x == points[j].x returns null. + */ + public static LagrangePolynomial[] lagrangePolynomials(Polynomial.Point[] points,Arithmetic arithmetic) { + Polynomial one = new Polynomial(new BigInteger[]{BigInteger.ONE},arithmetic); + LagrangePolynomial[] lagrangePolynomials = new LagrangePolynomial[points.length]; + Polynomial[] factors = new Polynomial[points.length]; + for (int i = 0 ; i < factors.length ; i++){ + factors[i] = new Polynomial(new BigInteger[]{points[i].x.negate(),BigInteger.ONE},arithmetic); // X - Xi + } + Polynomial product; + BigInteger divisor; + for(int i = 0; i < points.length; i ++) { + product = one; + divisor = BigInteger.ONE; + for (int j = 0; j < points.length; j++) { + if (i != j) { + divisor = arithmetic.mul(divisor,arithmetic.sub(points[i].x,points[j].x)); + product = product.mul(factors[j]); + } + } + if(divisor.equals(BigInteger.ZERO)) + return null; + lagrangePolynomials[i] = new LagrangePolynomial(product,points[i].y,divisor); + } + return lagrangePolynomials; + } +} diff --git a/distributed-key-generation/src/main/java/meerkat/crypto/secretsharing/shamir/Polynomial.java b/distributed-key-generation/src/main/java/meerkat/crypto/secretsharing/shamir/Polynomial.java new file mode 100644 index 0000000..58f38fb --- /dev/null +++ b/distributed-key-generation/src/main/java/meerkat/crypto/secretsharing/shamir/Polynomial.java @@ -0,0 +1,208 @@ +package meerkat.crypto.secretsharing.shamir; + +import meerkat.crypto.utils.Arithmetic; + +import java.math.BigInteger; +import java.util.Arrays; + +/** + * Created by Tzlil on 1/27/2016. + */ +public class Polynomial implements Comparable { + private final int degree; + private final BigInteger[] coefficients; + private final Arithmetic arithmetic; + + /** + * constructor + * @param coefficients + * @param arithmetic + * degree set as max index such that coefficients[degree] not equals zero + */ + public Polynomial(BigInteger[] coefficients,Arithmetic arithmetic) { + int d = coefficients.length - 1; + while (d > 0 && coefficients[d].equals(BigInteger.ZERO)){ + d--; + } + this.degree = d; + this.coefficients = coefficients; + this.arithmetic = arithmetic; + } + + /** + * Compare to another polynomial (order by degree, then coefficients). + */ + @Override + public int compareTo(Polynomial other) { + if (this.degree != other.degree) + return this.degree - other.degree; + int compare; + for (int i = degree; i >= degree ; i--){ + compare = this.coefficients[i].compareTo(other.coefficients[i]); + if (compare != 0){ + return compare; + } + } + return 0; + } + + /** + * @param x + * @return sum of coefficients[i] * (x ^ i) + */ + public BigInteger evaluate(BigInteger x){ + BigInteger result = BigInteger.ZERO; + BigInteger power = BigInteger.ONE; + for(int i = 0 ; i <= degree ; i++){ + result = arithmetic.add(result,arithmetic.mul(coefficients[i],power)); + power = power.multiply(x); + } + return result; + } + + /** + * @param points + * @return polynomial of minimal degree which goes through all points. + * If there exists i != j s.t points[i].x == points[j].x, method returns null. + */ + public static Polynomial interpolation(Point[] points, Arithmetic arithmetic) { + LagrangePolynomial[] l = LagrangePolynomial.lagrangePolynomials(points,arithmetic); + if (l == null){ + return null; + } + // product = product of l[i].divisor + BigInteger product = BigInteger.ONE; + for (int i = 0; i < l.length;i++){ + product = arithmetic.mul(product,l[i].divisor); + } + + // factor[i] = product divided by l[i].divisor = product of l[j].divisor s.t j!=i + BigInteger[] factors = new BigInteger[l.length]; + for (int i = 0; i < l.length;i++){ + factors[i] = arithmetic.div(product,l[i].divisor); + } + int degree = l[0].polynomial.degree; + + // coefficients[j] = (sum of l[i].evaluate * factor[i] * l[i].coefficients[j] s.t i!=j) divide by product = + // = sum of l[i].evaluate * l[i].coefficients[j] / l[i].divisor s.t i!=j + BigInteger[] coefficients = new BigInteger[degree + 1]; + for (int j = 0; j < coefficients.length;j++){ + coefficients[j] = BigInteger.ZERO; + for (int i = 0; i < l.length; i++){ + BigInteger current = arithmetic.mul(l[i].image,factors[i]); + current = arithmetic.mul(current,l[i].polynomial.coefficients[j]); + coefficients[j] = arithmetic.add(coefficients[j],current); + } + coefficients[j] = arithmetic.div(coefficients[j],product); + } + return new Polynomial(coefficients,arithmetic); + } + + /** + * @param other + * @return new Polynomial of degree max(this degree,other degree) s.t for all x + * new.evaluate(x) = this.evaluate(x) + other.evaluate(x) + */ + public Polynomial add(Polynomial other){ + Polynomial bigger,smaller; + if(this.degree < other.degree){ + bigger = other; + smaller = this; + }else{ + bigger = this; + smaller = other; + } + BigInteger[] coefficients = bigger.getCoefficients(); + + for (int i = 0; i <= smaller.degree ; i++){ + coefficients[i] = arithmetic.add(smaller.coefficients[i],bigger.coefficients[i]); + } + return new Polynomial(coefficients,other.arithmetic); + } + + /** + * @param constant + * @return new Polynomial of degree this.degree s.t for all x + * new.evaluate(x) = constant * this.evaluate(x) + */ + public Polynomial mul(BigInteger constant){ + + BigInteger[] coefficients = this.getCoefficients(); + + for (int i = 0; i <= this.degree ; i++){ + coefficients[i] = arithmetic.mul(constant,coefficients[i]); + } + return new Polynomial(coefficients,arithmetic); + } + + /** + * @param other + * @return new Polynomial of degree this degree + other degree + 1 s.t for all x + * new.evaluate(x) = this.evaluate(x) * other.evaluate(x) + */ + public Polynomial mul(Polynomial other){ + + BigInteger[] coefficients = new BigInteger[this.degree + other.degree + 1]; + Arrays.fill(coefficients,BigInteger.ZERO); + + for (int i = 0; i <= this.degree ; i++){ + for (int j = 0; j <= other.degree; j++){ + coefficients[i+j] = arithmetic.add(coefficients[i+j],arithmetic.mul(this.coefficients[i],other.coefficients[j])); + } + } + return new Polynomial(coefficients,arithmetic); + } + + + /** getter + * @return copy of coefficients + */ + public BigInteger[] getCoefficients() { + return Arrays.copyOf(coefficients,coefficients.length); + } + + /** getter + * @return degree + */ + public int getDegree() { + return degree; + } + + /** + * inner class + * container for (x,y) x from range and y from evaluate of polynomial + */ + public static class Point implements java.io.Serializable { + public final BigInteger x; + public final BigInteger y; + + /** + * constructor + * @param x + * @param polynomial y = polynomial.evaluate(x) + */ + public Point(BigInteger x, Polynomial polynomial) { + this.x = x; + this.y = polynomial.evaluate(x); + } + + /** + * constructor + * @param x + * @param y + */ + public Point(BigInteger x,BigInteger y) { + this.x = x; + this.y = y; + } + + @Override + public boolean equals(Object obj) { + if(!super.equals(obj)) + return false; + Point other = (Point)obj; + return this.x.equals(other.x) && this.y.equals(other.y); + } + } + +} diff --git a/distributed-key-generation/src/main/java/meerkat/crypto/secretsharing/shamir/SecretSharing.java b/distributed-key-generation/src/main/java/meerkat/crypto/secretsharing/shamir/SecretSharing.java new file mode 100644 index 0000000..fb86993 --- /dev/null +++ b/distributed-key-generation/src/main/java/meerkat/crypto/secretsharing/shamir/SecretSharing.java @@ -0,0 +1,123 @@ +package meerkat.crypto.secretsharing.shamir; + +import meerkat.crypto.utils.Arithmetic; +import meerkat.crypto.utils.concrete.Fp; + +import java.math.BigInteger; +import java.util.Random; + +/** + * Created by Tzlil on 1/27/2016. + * an implementation of Shamire's secret sharing scheme + */ +public class SecretSharing{ + /** + * threshold + */ + protected final int t; + /** + * number of shares + */ + protected final int n; + /** + * a large prime + */ + protected final BigInteger q; + /** + * random polynomial of degree s.t polynomial.evaluate(0) = secret + */ + protected final Polynomial polynomial; + + /** + * constructor + * @param q a large prime. + * @param t threshold. Any t+1 share holders can recover the secret, + * but any set of at most t share holders cannot + * @param n number of share holders + * @param zi secret, chosen from Zq + * @param random use for generate random polynomial + */ + public SecretSharing(int t, int n, BigInteger zi, Random random, BigInteger q) { + this.q = q; + this.t = t; + this.n = n; + this.polynomial = generateRandomPolynomial(zi,random); + } + + /** + * @param x + * @param random + * @return new Polynomial polynomial of degree t ,such that + * 1. polynomial(0) = x + * 2. polynomial coefficients randomly chosen from Zq (except of coefficients[0] = x) + */ + private Polynomial generateRandomPolynomial(BigInteger x, Random random) { + BigInteger[] coefficients = new BigInteger[t + 1]; + coefficients[0] = x.mod(q); + int bits = q.bitLength(); + for (int i = 1 ; i <= t; i++ ){ + coefficients[i] = new BigInteger(bits,random).mod(q); + } + return new Polynomial(coefficients,new Fp(q)); + } + + /** + * @param i in range of [1,...n] + * + * @return polynomial.evaluate(i) + */ + public Polynomial.Point getShare(int i){ + assert (i > 0 && i <= n); + return new Polynomial.Point(BigInteger.valueOf(i), polynomial); + } + + /** + * @param shares - subset of the original shares + * + * @return evaluate of interpolation(shares) at x = 0 + */ + public static BigInteger recoverSecret(Polynomial.Point[] shares, Arithmetic arithmetic) { + return recoverPolynomial(shares,arithmetic).evaluate(BigInteger.ZERO); + } + /** + * @param shares - subset of the original shares + * + * @return interpolation(shares) + */ + public static Polynomial recoverPolynomial(Polynomial.Point[] shares, Arithmetic arithmetic) { + return Polynomial.interpolation(shares,arithmetic); + } + + /** + * getter + * @return threshold + */ + public int getT() { + return t; + } + + /** + * getter + * @return number of share holders + */ + public int getN() { + return n; + } + + /** + * getter + * @return the prime was given in the constructor + */ + public BigInteger getQ() { + return q; + } + + + /** + * getter + * @return the polynomial was generated in constructor + */ + public Polynomial getPolynomial() { + return polynomial; + } +} diff --git a/distributed-key-generation/src/main/java/meerkat/crypto/utils/Arithmetic.java b/distributed-key-generation/src/main/java/meerkat/crypto/utils/Arithmetic.java new file mode 100644 index 0000000..6221a5c --- /dev/null +++ b/distributed-key-generation/src/main/java/meerkat/crypto/utils/Arithmetic.java @@ -0,0 +1,38 @@ +package meerkat.crypto.utils; + +/** + * Created by Tzlil on 3/17/2016. + * defines the properties of the traditional operations : add,sub,mul,div + * between two objects of type T + */ +public interface Arithmetic { + /** + * addition + * @param a + * @param b + * @return a + b + */ + T add(T a, T b); + /** + * subtraction + * @param a + * @param b + * @return a - b + */ + T sub(T a, T b); + /** + * multiplication + * @param a + * @param b + * @return a * b + */ + T mul(T a, T b); + /** + * division + * @param a + * @param b + * @return a / b + */ + T div(T a, T b); + +} diff --git a/distributed-key-generation/src/main/java/meerkat/crypto/utils/concrete/Fp.java b/distributed-key-generation/src/main/java/meerkat/crypto/utils/concrete/Fp.java new file mode 100644 index 0000000..52fb324 --- /dev/null +++ b/distributed-key-generation/src/main/java/meerkat/crypto/utils/concrete/Fp.java @@ -0,0 +1,44 @@ +package meerkat.crypto.utils.concrete; + +import meerkat.crypto.utils.Arithmetic; +import org.factcenter.qilin.primitives.concrete.Zpstar; + +import java.math.BigInteger; + +/** + * Created by Tzlil on 3/17/2016. + * an implementation of Arithmetic over prime fields: integers modulo p + */ +public class Fp implements Arithmetic { + public final BigInteger p; + private final Zpstar zp; + + /** + * constructor + * @param p prime + */ + public Fp(BigInteger p) { + this.p = p; + this.zp = new Zpstar(p); + } + + @Override + public BigInteger add(BigInteger a, BigInteger b){ + return a.add(b).mod(p); + } + + @Override + public BigInteger sub(BigInteger a, BigInteger b){ + return a.add(p).subtract(b).mod(p); + } + + @Override + public BigInteger mul(BigInteger a, BigInteger b){ + return zp.add(a,b); + } + + @Override + public BigInteger div(BigInteger a, BigInteger b){ + return mul(a,zp.negate(b)); + } +} diff --git a/distributed-key-generation/src/main/proto/meerkat/DKG.proto b/distributed-key-generation/src/main/proto/meerkat/DKG.proto new file mode 100644 index 0000000..3457fbe --- /dev/null +++ b/distributed-key-generation/src/main/proto/meerkat/DKG.proto @@ -0,0 +1,46 @@ +syntax = "proto3"; + +package meerkat; + +option java_package = "meerkat.protobuf"; + +message Payload { + enum Type { + SHARE = 0; + COMMITMENT = 1; + COMPLAINT = 2; + DONE = 3; + ANSWER = 4; + YCOMMITMENT = 5; + YCOMPLAINT = 6; + YANSWER = 7; + ABORT = 8; + } + + + // Type of message in protocol + Type type = 1; + + oneof payload_data { + IDMessage id = 5; + ShareMessage share = 6; + CommitmentMessage commitment = 7; + } +} + +message IDMessage { + int32 id = 1; +} + +message ShareMessage { + int32 i = 1; + int32 j = 2; + bytes share = 3; + // For double shares (used in GJKR protocol) + bytes share_t = 4; +} + +message CommitmentMessage { + int32 k = 1; + bytes commitment = 2; +} diff --git a/distributed-key-generation/src/test/java/meerkat/crypto/dkg/feldman/DKGMaliciousUser.java b/distributed-key-generation/src/test/java/meerkat/crypto/dkg/feldman/DKGMaliciousUser.java new file mode 100644 index 0000000..78b70d4 --- /dev/null +++ b/distributed-key-generation/src/test/java/meerkat/crypto/dkg/feldman/DKGMaliciousUser.java @@ -0,0 +1,73 @@ +package meerkat.crypto.dkg.feldman; + +import meerkat.comm.Channel; + +import java.math.BigInteger; +import java.util.*; + +/** + * Created by Tzlil on 3/21/2016. + */ +public class DKGMaliciousUser extends User { + + private final Protocol maliciousDkg; + private final Set falls; + public DKGMaliciousUser(Protocol dkg, Protocol maliciousDKG, Channel channel, Set falls) { + super(dkg, channel); + this.falls = falls; + this.maliciousDkg = maliciousDKG; + maliciousDKG.setParties(parties); + } + + public static Set selectFallsRandomly(Set ids, Random random){ + Set falls = new HashSet(); + ArrayList idsList = new ArrayList(); + for (int id : ids){ + idsList.add(id); + } + int fallsSize = random.nextInt(idsList.size()) + 1;// 1 - (n-1) + while (falls.size() < fallsSize){ + falls.add(idsList.remove(random.nextInt(idsList.size()))); + } + return falls; + } + + public static Protocol generateMaliciousDKG(Protocol dkg,Channel channel,Random random){ + BigInteger q = dkg.getQ(); + BigInteger zi = new BigInteger(q.bitLength(), random).mod(q); + Protocol malicious = new Protocol(dkg.getT(),dkg.getN(),zi,random,dkg.getQ() + ,dkg.getGenerator(),dkg.getGroup(),dkg.getId(),dkg.getEncoder()); + malicious.setChannel(channel); + return malicious; + } + + @Override + public void stage1() { + dkg.broadcastCommitments(); + sendSecrets(); //insteadof crypto.sendSecrets(channel); + } + + @Override + public void stage3() { + maliciousDkg.answerAllComplainingPlayers(); + } + + @Override + public void stage4(){ + // do nothing + } + + private void sendSecrets(){ + for (int j = 1; j <= n ; j++){ + if(j != id){ + if(falls.contains(j)){ + maliciousDkg.sendSecret(j); + }else { + dkg.sendSecret(j); + } + } + } + } + + +} diff --git a/distributed-key-generation/src/test/java/meerkat/crypto/dkg/feldman/DKGTest.java b/distributed-key-generation/src/test/java/meerkat/crypto/dkg/feldman/DKGTest.java new file mode 100644 index 0000000..f522a42 --- /dev/null +++ b/distributed-key-generation/src/test/java/meerkat/crypto/dkg/feldman/DKGTest.java @@ -0,0 +1,170 @@ +package meerkat.crypto.dkg.feldman; + +import meerkat.comm.ChannelImpl; +import meerkat.crypto.utils.Arithmetic; +import meerkat.crypto.utils.concrete.Fp; +import meerkat.comm.Channel; +import meerkat.crypto.secretsharing.feldman.VerifiableSecretSharing; +import meerkat.crypto.secretsharing.shamir.Polynomial; +import meerkat.crypto.secretsharing.shamir.SecretSharing; +import meerkat.crypto.utils.BigIntegerByteEncoder; +import meerkat.crypto.utils.GenerateRandomPrime; +import org.factcenter.qilin.primitives.Group; +import org.factcenter.qilin.primitives.concrete.Zpstar; +import org.factcenter.qilin.util.ByteEncoder; +import org.junit.Test; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; + +/** + * Created by Tzlil on 3/21/2016. + */ +public class DKGTest { + + int tests = 10; + BigInteger p = GenerateRandomPrime.SafePrime100Bits; + BigInteger q = p.subtract(BigInteger.ONE).divide(BigInteger.valueOf(2)); + Group group = new Zpstar(p); + Arithmetic arithmetic = new Fp(q); + int t = 9; + int n = 20; + + public void oneTest(Testable testable) throws Exception { + for (int i = 0; i < testable.threads.length ; i++){ + testable.threads[i].start(); + } + for (int i = 0; i < testable.threads.length ; i++){ + testable.threads[i].join(); + } + + // got the right public value + BigInteger publicValue = group.multiply(testable.g,testable.secret); + for (int i: testable.valids){ + assert (testable.dkgs[i - 1].getPublicValue().equals(publicValue)); + } + + // assert valid verification values + BigInteger expected,verification; + for (int i: testable.valids){ + expected = group.multiply(testable.g, testable.dkgs[i - 1].getShare().y); + verification = VerifiableSecretSharing.computeVerificationValue(i, testable.dkgs[i - 1].getCommitments(), group); + assert (expected.equals(verification)); + } + + + // restore the secret from shares + ArrayList sharesList = new ArrayList(); + + for (int i: testable.valids){ + sharesList.add(testable.dkgs[i - 1].getShare()); + } + Polynomial.Point[] shares = new Polynomial.Point[sharesList.size()]; + for (int i = 0; i < shares.length; i ++){ + shares[i] = sharesList.get(i); + } + + BigInteger calculatedSecret = SecretSharing.recoverSecret(shares,arithmetic); + assert (calculatedSecret.equals(testable.secret)); + } + + + @Test + public void test() throws Exception { + Testable testable; + for (int i = 0; i < tests; i++){ + testable = new Testable(new Random()); + oneTest(testable); + } + } + + class Testable{ + Set valids; + Set QUAL; + Set aborted; + Set malicious; + User[] dkgs; + Thread[] threads; + BigInteger g; + BigInteger secret; + + public Testable(Random random) { + this.dkgs = new User[n]; + this.valids = new HashSet(); + this.QUAL = new HashSet(); + this.aborted = new HashSet(); + this.malicious = new HashSet(); + this.threads = new Thread[n]; + this.g = sampleGenerator(random); + ArrayList ids = new ArrayList(); + for (int id = 1; id<= n ; id++){ + ids.add(id); + } + int id; + BigInteger s; + Protocol dkg; + this.secret = BigInteger.ZERO; + ChannelImpl channels = new ChannelImpl(); + ByteEncoder byteEncoder = new BigIntegerByteEncoder(); + while (!ids.isEmpty()) { + id = ids.remove(random.nextInt(ids.size())); + Channel channel = channels.getChannel(id); + s = randomIntModQ(random); + dkg = new meerkat.crypto.dkg.feldman.Protocol(t, n, s, random, q, g, group, id,byteEncoder); + dkgs[id - 1] = randomDKGUser(id,channel,dkg,random); + threads[id - 1] = new Thread(dkgs[id - 1]); + if(QUAL.contains(id)){ + this.secret = this.secret.add(s).mod(q); + } + } + + } + + public User randomDKGUser(int id, Channel channel, Protocol dkg, Random random){ + if (QUAL.size() <= t) { + valids.add(id); + QUAL.add(id); + return new User(dkg,channel); + }else{ + int type = random.nextInt(3); + switch (type){ + case 0:// regular + valids.add(id); + QUAL.add(id); + return new User(dkg,channel); + case 1:// abort + int abortStage = random.nextInt(2) + 1; // 1 or 2 + aborted.add(id); + if (abortStage == 2){ + QUAL.add(id); + } + return new DKGUserImplAbort(dkg,channel,abortStage); + case 2:// malicious + malicious.add(id); + Set falls = DKGMaliciousUser.selectFallsRandomly(valids,random); + Protocol maliciousDKG = DKGMaliciousUser.generateMaliciousDKG(dkg,channel,random); + return new DKGMaliciousUser(dkg,maliciousDKG,channel,falls); + default: + return null; + } + } + } + + public BigInteger sampleGenerator(Random random){ + BigInteger ZERO = group.zero(); + BigInteger g; + do { + g = group.sample(random); + } while (!g.equals(ZERO) && !group.multiply(g, q).equals(ZERO)); + return g; + } + + public BigInteger randomIntModQ(Random random){ + return new BigInteger(q.bitLength(), random).mod(q); + } + + } +} diff --git a/distributed-key-generation/src/test/java/meerkat/crypto/dkg/feldman/DKGUserImplAbort.java b/distributed-key-generation/src/test/java/meerkat/crypto/dkg/feldman/DKGUserImplAbort.java new file mode 100644 index 0000000..528ea9f --- /dev/null +++ b/distributed-key-generation/src/test/java/meerkat/crypto/dkg/feldman/DKGUserImplAbort.java @@ -0,0 +1,65 @@ +package meerkat.crypto.dkg.feldman; + +import meerkat.comm.Channel; +import meerkat.protobuf.DKG; + +import static meerkat.crypto.dkg.comm.MessageUtils.createMessage; + +/** + * Created by Tzlil on 3/14/2016. + */ +public class DKGUserImplAbort extends User { + + final int abortStage; + int stage; + public DKGUserImplAbort(Protocol dkg, Channel channel, int abortStage) { + super(dkg, channel); + this.abortStage = abortStage;// 1 - 2 + this.stage = 1; + } + + + private void sendAbort(){ + channel.broadcastMessage(createMessage(DKG.Payload.Type.ABORT)); + } + + @Override + protected void stage1() { + if(stage < abortStage) + super.stage1(); + else if(stage == abortStage){ + sendAbort(); + } + stage++; + } + + @Override + protected void stage2() { + if(stage < abortStage) + super.stage2(); + else if(stage == abortStage){ + sendAbort(); + } + stage++; + } + + @Override + protected void stage3() { + if(stage < abortStage) + super.stage3(); + else if(stage == abortStage){ + sendAbort(); + } + stage++; + } + + @Override + protected void stage4() { + if(stage < abortStage) + super.stage4(); + else if(stage == abortStage){ + sendAbort(); + } + stage++; + } +} diff --git a/distributed-key-generation/src/test/java/meerkat/crypto/dkg/gjkr/SDKGMaliciousUserImpl.java b/distributed-key-generation/src/test/java/meerkat/crypto/dkg/gjkr/SDKGMaliciousUserImpl.java new file mode 100644 index 0000000..baf1a81 --- /dev/null +++ b/distributed-key-generation/src/test/java/meerkat/crypto/dkg/gjkr/SDKGMaliciousUserImpl.java @@ -0,0 +1,61 @@ +package meerkat.crypto.dkg.gjkr; + +import meerkat.comm.Channel; + +import java.math.BigInteger; +import java.util.Random; +import java.util.Set; + +/** + * Created by Tzlil on 3/29/2016. + */ +public class SDKGMaliciousUserImpl extends User { + + private final Protocol maliciousSDKG; + private final Set falls; + public SDKGMaliciousUserImpl(Protocol sdkg, Protocol maliciousSDKG + , Channel channel, Set falls) { + super(sdkg, channel); + this.falls = falls; + this.maliciousSDKG = maliciousSDKG; + maliciousSDKG.setParties(parties); + } + + public static Protocol generateMaliciousSDKG(Protocol sdkg,Channel channel,Random random){ + BigInteger q = sdkg.getQ(); + BigInteger zi = new BigInteger(q.bitLength(), random).mod(q); + Protocol malicious = new Protocol(sdkg.getT(),sdkg.getN(),zi,random,sdkg.getQ() + ,sdkg.getGenerator(),sdkg.getH(),sdkg.getGroup(),sdkg.getId(),sdkg.getEncoder()); + malicious.setChannel(channel); + return malicious; + } + + @Override + public void stage1() { + sdkg.computeAndBroadcastVerificationValues(); + sendSecrets(); //insteadof crypto.sendSecrets(channel); + } + + @Override + public void stage3() { + maliciousSDKG.answerAllComplainingPlayers(); + } + + @Override + public void stage4(){ + //do nothing + } + + private void sendSecrets(){ + for (int j = 1; j <= n ; j++){ + if(j != id){ + if(falls.contains(j)){ + maliciousSDKG.sendSecret(j); + }else { + sdkg.sendSecret(j); + } + } + } + } + +} diff --git a/distributed-key-generation/src/test/java/meerkat/crypto/dkg/gjkr/SDKGTest.java b/distributed-key-generation/src/test/java/meerkat/crypto/dkg/gjkr/SDKGTest.java new file mode 100644 index 0000000..e4550f0 --- /dev/null +++ b/distributed-key-generation/src/test/java/meerkat/crypto/dkg/gjkr/SDKGTest.java @@ -0,0 +1,206 @@ +package meerkat.crypto.dkg.gjkr; + +import meerkat.comm.ChannelImpl; +import meerkat.crypto.utils.Arithmetic; +import meerkat.crypto.utils.concrete.Fp; +import meerkat.comm.Channel; +import meerkat.crypto.secretsharing.feldman.VerifiableSecretSharing; +import meerkat.crypto.dkg.feldman.DKGMaliciousUser; +import meerkat.crypto.secretsharing.shamir.Polynomial; +import meerkat.crypto.secretsharing.shamir.SecretSharing; +import meerkat.crypto.utils.BigIntegerByteEncoder; +import meerkat.crypto.utils.GenerateRandomPrime; +import org.factcenter.qilin.primitives.Group; +import org.factcenter.qilin.primitives.concrete.Zpstar; +import org.factcenter.qilin.util.ByteEncoder; +import org.junit.Assert; +import org.junit.Test; +import org.junit.internal.runners.statements.Fail; + +import static org.junit.Assert.*; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +/** + * Created by Tzlil on 3/29/2016. + * TODO: Separate into multiple tests, + * TODO: Make tests deterministic (using constant seed for random generator) + */ +public class SDKGTest { + + private ExecutorService executorService = Executors.newCachedThreadPool(); + + final static int NUM_TESTS = 10; + BigInteger p = GenerateRandomPrime.SafePrime100Bits; + BigInteger q = p.subtract(BigInteger.ONE).divide(BigInteger.valueOf(2)); + Group group = new Zpstar(p); + Arithmetic arithmetic = new Fp(q); + int t = 1; + int n = 20; + Random rand = new Random(1); + + public void oneTest(Testable testable) throws Exception { + for (int i = 0; i < testable.sdkgs.length ; i++){ + testable.futures[i] = executorService.submit(testable.sdkgs[i]); + } + for (int i = 0; i < testable.futures.length ; i++){ + testable.futures[i].get(); + } + + // got the right public value + BigInteger publicValue = group.multiply(testable.g,testable.secret); + for (int i: testable.valids){ + assert (testable.sdkgs[i - 1].getPublicValue().equals(publicValue)); + } + + // assert valid verification values + BigInteger expected,verification; + for (int i: testable.valids){ + expected = group.multiply(testable.g, testable.sdkgs[i - 1].getShare().y); + verification = VerifiableSecretSharing.computeVerificationValue(i, testable.sdkgs[i - 1].getCommitments(), group); + assert (expected.equals(verification)); + } + + + // restore the secret from shares + ArrayList sharesList = new ArrayList(); + + for (int i: testable.valids){ + sharesList.add(testable.sdkgs[i - 1].getShare()); + } + Polynomial.Point[] shares = new Polynomial.Point[sharesList.size()]; + for (int i = 0; i < shares.length; i ++){ + shares[i] = sharesList.get(i); + } + + BigInteger calculatedSecret = SecretSharing.recoverSecret(shares,arithmetic); + assert (calculatedSecret.equals(testable.secret)); + } + + + @Test + public void test() throws Exception { + Testable testable; + for (int i = 0; i < NUM_TESTS; i++) { + testable = new Testable(n, t, group, q, rand); + oneTest(testable); + } + } + + static class Testable { + Set valids; + Set QUAL; + Set aborted; + Set malicious; + User[] sdkgs; + Future[] futures; + BigInteger g; + BigInteger h; + BigInteger secret; + Group group; + int n; + int t; + BigInteger q; + Random random; + ChannelImpl channels = new ChannelImpl(); + + public Testable(int n, int t, Group group, BigInteger q, Random random) { + this.n = n; + this.t = t; + this.group = group; + this.q = q; + this.random = random; + this.sdkgs = new User[n]; + this.valids = new HashSet(); + this.QUAL = new HashSet(); + this.aborted = new HashSet(); + this.malicious = new HashSet(); + this.futures = new Future[n]; + this.g = sampleGenerator(random); + this.h = group.multiply(g,randomIntModQ(random)); + ArrayList ids = new ArrayList(); + for (int id = 1; id<= n ; id++){ + ids.add(id); + } + int id; + BigInteger s; + Channel channel; + Protocol sdkg; + this.secret = BigInteger.ZERO; + ByteEncoder encoder = new BigIntegerByteEncoder(); + while (!ids.isEmpty()) { + id = ids.remove(random.nextInt(ids.size())); + s = randomIntModQ(random); + channel = channels.getChannel(id); + sdkg = new Protocol(t, n, s, random, q, g , h, group, id,encoder); + sdkgs[id - 1] = randomSDKGUser(id,channel,sdkg); + if(QUAL.contains(id)){ + this.secret = this.secret.add(s).mod(q); + } + } + + } + + enum UserType { + HONEST, + FAILSTOP, + MALICIOUS, + } + + public User newSDKGUser(int id, Channel channel, Protocol sdkg, UserType userType) { + switch(userType) { + case HONEST: + valids.add(id); + QUAL.add(id); + return new User(sdkg,channel); + + case FAILSTOP: + int abortStage = random.nextInt(3) + 1; // 1 or 2 or 3 + aborted.add(id); + if (abortStage > 1){ + QUAL.add(id); + } + return new SDKGUserImplAbort(sdkg,channel,abortStage); + + case MALICIOUS: + malicious.add(id); + Set falls = DKGMaliciousUser.selectFallsRandomly(valids,random); + Protocol maliciousSDKG = SDKGMaliciousUserImpl.generateMaliciousSDKG(sdkg,channel,random); + return new SDKGMaliciousUserImpl(sdkg,maliciousSDKG,channel,falls); + + } + fail("Unknown user type"); + return null; + } + + public User randomSDKGUser(int id, Channel channel, Protocol sdkg){ + if (QUAL.size() <= t) { + return newSDKGUser(id, channel, sdkg, UserType.HONEST); + } else { + UserType type = UserType.values()[random.nextInt(UserType.values().length)]; + return newSDKGUser(id, channel, sdkg, type); + } + } + + public BigInteger sampleGenerator(Random random){ + BigInteger ZERO = group.zero(); + BigInteger g; + do { + g = group.sample(random); + } while (!g.equals(ZERO) && !group.multiply(g, q).equals(ZERO)); + return g; + } + + public BigInteger randomIntModQ(Random random){ + return new BigInteger(q.bitLength(), random).mod(q); + } + + } +} diff --git a/distributed-key-generation/src/test/java/meerkat/crypto/dkg/gjkr/SDKGUserImplAbort.java b/distributed-key-generation/src/test/java/meerkat/crypto/dkg/gjkr/SDKGUserImplAbort.java new file mode 100644 index 0000000..5b0e11b --- /dev/null +++ b/distributed-key-generation/src/test/java/meerkat/crypto/dkg/gjkr/SDKGUserImplAbort.java @@ -0,0 +1,80 @@ +package meerkat.crypto.dkg.gjkr; + +import com.google.protobuf.InvalidProtocolBufferException; +import meerkat.crypto.dkg.comm.MessageUtils; +import meerkat.comm.Channel; +import meerkat.protobuf.Comm; +import meerkat.protobuf.DKG; + +/** + * Created by Tzlil on 3/14/2016. + */ +public class SDKGUserImplAbort extends User { + + public static class AbortException extends RuntimeException { + + } + + final int abortStage; + int stage; + public SDKGUserImplAbort(Protocol sdkg, Channel channel, int abortStage) { + super(sdkg, channel); + this.abortStage = abortStage;// 1 - 4 + this.stage = 1; + } + + private void abort(){ + //stopReceiver(); + channel.broadcastMessage(MessageUtils.createMessage(DKG.Payload.Type.ABORT)); + throw new AbortException(); + } + + @Override + protected void stage1() { + if(stage < abortStage) + super.stage1(); + else if(stage == abortStage){ + abort(); + } + stage++; + } + + @Override + protected void stage2() { + if(stage < abortStage) + super.stage2(); + else if(stage == abortStage){ + abort(); + } + stage++; + } + + @Override + protected void stage3() { + if(stage < abortStage) + super.stage3(); + else if(stage == abortStage){ + abort(); + } + stage++; + } + + @Override + protected void stage4() { + if(stage < abortStage) + super.stage4(); + else if(stage == abortStage){ + abort(); + } + stage++; + } + + @Override + public void run() { + try { + super.run(); + } catch (AbortException e) { + // Expected + } + } +} diff --git a/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/feldman/VerifiableSecretSharingTest.java b/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/feldman/VerifiableSecretSharingTest.java new file mode 100644 index 0000000..7992dfb --- /dev/null +++ b/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/feldman/VerifiableSecretSharingTest.java @@ -0,0 +1,68 @@ +package meerkat.crypto.secretsharing.feldman; + +import meerkat.crypto.secretsharing.shamir.Polynomial; +import org.factcenter.qilin.primitives.Group; +import org.factcenter.qilin.primitives.concrete.Zpstar; +import org.junit.Before; +import org.junit.Test; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Random; + +/** + * Created by Tzlil on 1/29/2016. + */ +public class VerifiableSecretSharingTest { + + + VerifiableSecretSharing[] verifiableSecretSharingArray; + int tests = 1 << 10; + Random random; + + @Before + public void settings(){ + BigInteger p = BigInteger.valueOf(2903); + BigInteger q = p.subtract(BigInteger.ONE).divide(BigInteger.valueOf(2)); + Zpstar zpstar = new Zpstar(p); + random = new Random(); + BigInteger g; + BigInteger ZERO = zpstar.zero(); + do{ + g = zpstar.sample(random); + }while (!g.equals(ZERO) && !zpstar.multiply(g,q).equals(ZERO));// sample from QRZp* + int t = 8; + int n = 20; + verifiableSecretSharingArray = new VerifiableSecretSharing[tests]; + for (int i = 0; i < verifiableSecretSharingArray.length; i++){ + verifiableSecretSharingArray[i] = new VerifiableSecretSharing(t,n + ,new BigInteger(q.bitLength(),random).mod(q),random,q,g,zpstar); + } + } + + public void oneTest(VerifiableSecretSharing verifiableSecretSharing) throws Exception { + int n = verifiableSecretSharing.getN(); + Group zpstar = verifiableSecretSharing.getGroup(); + BigInteger g = verifiableSecretSharing.getGenerator(); + Polynomial.Point[] shares = new Polynomial.Point[n]; + ArrayList commitments = verifiableSecretSharing.getCommitmentsArrayList(); + BigInteger[] verifications = new BigInteger[n]; + for (int i = 1 ; i <= shares.length; i ++){ + shares[i - 1] = verifiableSecretSharing.getShare(i); + verifications[i - 1] = VerifiableSecretSharing.computeVerificationValue(i,commitments,zpstar); + } + BigInteger expected; + for (int i = 0 ; i < shares.length ; i++){ + expected = zpstar.multiply(g,shares[i].y); + assert (expected.equals(verifications[i])); + } + + } + + @Test + public void secretSharingTest() throws Exception { + for (int i = 0 ; i < verifiableSecretSharingArray.length; i ++){ + oneTest(verifiableSecretSharingArray[i]); + } + } +} diff --git a/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/PolynomialTests/AddTest.java b/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/PolynomialTests/AddTest.java new file mode 100644 index 0000000..79a9aef --- /dev/null +++ b/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/PolynomialTests/AddTest.java @@ -0,0 +1,46 @@ +package meerkat.crypto.secretsharing.shamir.PolynomialTests; +import meerkat.crypto.utils.GenerateRandomPolynomial; +import meerkat.crypto.utils.Z; +import meerkat.crypto.secretsharing.shamir.Polynomial; +import org.junit.Before; +import org.junit.Test; + +import java.math.BigInteger; +import java.util.Random; + +/** + * Created by Tzlil on 1/27/2016. + */ +public class AddTest { + + Polynomial[] arr1; + Polynomial[] arr2; + int tests = 1 << 12; + int maxDegree = 15; + int bits = 128; + Random random; + + @Before + public void settings(){ + random = new Random(); + arr1 = new Polynomial[tests]; + arr2 = new Polynomial[tests]; + for (int i = 0; i < arr1.length; i++){ + arr1[i] = GenerateRandomPolynomial.generateRandomPolynomial(random.nextInt(maxDegree),bits,random,new Z()); + arr2[i] = GenerateRandomPolynomial.generateRandomPolynomial(random.nextInt(maxDegree),bits,random,new Z()); + } + } + + public void oneTest(Polynomial p1, Polynomial p2){ + Polynomial sum = p1.add(p2); + BigInteger x = new BigInteger(bits,random); + assert(sum.evaluate(x).equals(p1.evaluate(x).add(p2.evaluate(x)))); + } + + @Test + public void addTest(){ + for (int i = 0 ; i < arr1.length; i ++){ + oneTest(arr1[i],arr2[i]); + } + } +} diff --git a/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/PolynomialTests/InterpolationTest.java b/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/PolynomialTests/InterpolationTest.java new file mode 100644 index 0000000..ea857f8 --- /dev/null +++ b/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/PolynomialTests/InterpolationTest.java @@ -0,0 +1,68 @@ +package meerkat.crypto.secretsharing.shamir.PolynomialTests; + +import meerkat.crypto.secretsharing.shamir.Polynomial; +import meerkat.crypto.utils.Arithmetic; +import meerkat.crypto.utils.concrete.Fp; +import meerkat.crypto.utils.GenerateRandomPolynomial; +import meerkat.crypto.utils.GenerateRandomPrime; +import org.junit.Before; +import org.junit.Test; + +import java.math.BigInteger; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; + +/** + * Created by Tzlil on 1/27/2016. + */ +public class InterpolationTest { + Polynomial[] polynomials; + int tests = 1 << 10; + int maxDegree = 15; + int bits = 128; + Random random; + Polynomial.Point[][] pointsArrays; + Arithmetic arithmetic; + BigInteger p = GenerateRandomPrime.SafePrime100Bits; + + @Before + public void settings(){ + random = new Random(); + polynomials = new Polynomial[tests]; + pointsArrays = new Polynomial.Point[tests][]; + arithmetic = new Fp(p); + for (int i = 0; i < polynomials.length; i++){ + polynomials[i] = GenerateRandomPolynomial.generateRandomPolynomial(random.nextInt(maxDegree),bits,random,p); + pointsArrays[i] = randomPoints(polynomials[i]); + } + } + + public Polynomial.Point[] randomPoints(Polynomial polynomial){ + Polynomial.Point[] points = new Polynomial.Point[polynomial.getDegree() + 1]; + BigInteger x; + Set set = new HashSet(); + for (int i = 0; i < points.length; i++){ + x = new BigInteger(bits,random).mod(p); + if(set.contains(x)){ + i--; + continue; + } + set.add(x); + points[i] = new Polynomial.Point(x,polynomial); + } + return points; + } + + public void oneTest(Polynomial p, Polynomial.Point[] points) throws Exception { + Polynomial interpolation = Polynomial.interpolation(points,arithmetic); + assert (p.compareTo(interpolation) == 0); + } + + @Test + public void interpolationTest() throws Exception { + for (int i = 0; i < polynomials.length; i ++){ + oneTest(polynomials[i],pointsArrays[i]); + } + } +} diff --git a/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/PolynomialTests/MulByConstTest.java b/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/PolynomialTests/MulByConstTest.java new file mode 100644 index 0000000..77afc32 --- /dev/null +++ b/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/PolynomialTests/MulByConstTest.java @@ -0,0 +1,48 @@ +package meerkat.crypto.secretsharing.shamir.PolynomialTests; + +import meerkat.crypto.utils.GenerateRandomPolynomial; +import meerkat.crypto.utils.Z; +import meerkat.crypto.secretsharing.shamir.Polynomial; +import org.junit.Before; +import org.junit.Test; + +import java.math.BigInteger; +import java.util.Random; + +/** + * Created by Tzlil on 1/27/2016. + */ +public class MulByConstTest { + + + Polynomial[] arr1; + BigInteger[] arr2; + int tests = 1 << 12; + int maxDegree = 15; + int bits = 128; + Random random; + + @Before + public void settings(){ + random = new Random(); + arr1 = new Polynomial[tests]; + arr2 = new BigInteger[tests]; + for (int i = 0; i < arr1.length; i++){ + arr1[i] = GenerateRandomPolynomial.generateRandomPolynomial(random.nextInt(maxDegree),bits,random,new Z()); + arr2[i] = new BigInteger(bits,random); + } + } + + public void oneTest(Polynomial p, BigInteger c){ + Polynomial product = p.mul(c); + BigInteger x = new BigInteger(bits,random); + assert(product.evaluate(x).equals(p.evaluate(x).multiply(c))); + } + + @Test + public void mulByConstTest(){ + for (int i = 0 ; i < arr1.length; i ++){ + oneTest(arr1[i],arr2[i]); + } + } +} diff --git a/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/PolynomialTests/MulTest.java b/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/PolynomialTests/MulTest.java new file mode 100644 index 0000000..1f54247 --- /dev/null +++ b/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/PolynomialTests/MulTest.java @@ -0,0 +1,48 @@ +package meerkat.crypto.secretsharing.shamir.PolynomialTests; + +import meerkat.crypto.utils.GenerateRandomPolynomial; +import meerkat.crypto.utils.Z; +import meerkat.crypto.secretsharing.shamir.Polynomial; +import org.junit.Before; +import org.junit.Test; + +import java.math.BigInteger; +import java.util.Random; + +/** + * Created by Tzlil on 1/27/2016. + */ +public class MulTest { + + + Polynomial[] arr1; + Polynomial[] arr2; + int tests = 1 << 12; + int maxDegree = 15; + int bits = 128; + Random random; + + @Before + public void settings(){ + random = new Random(); + arr1 = new Polynomial[tests]; + arr2 = new Polynomial[tests]; + for (int i = 0; i < arr1.length; i++){ + arr1[i] = GenerateRandomPolynomial.generateRandomPolynomial(random.nextInt(maxDegree),bits,random,new Z()); + arr2[i] = GenerateRandomPolynomial.generateRandomPolynomial(random.nextInt(maxDegree),bits,random,new Z()); + } + } + + public void oneTest(Polynomial p1, Polynomial p2){ + Polynomial product = p1.mul(p2); + BigInteger x = new BigInteger(bits,random); + assert(product.evaluate(x).equals(p1.evaluate(x).multiply(p2.evaluate(x)))); + } + + @Test + public void mulTest(){ + for (int i = 0 ; i < arr1.length; i ++){ + oneTest(arr1[i],arr2[i]); + } + } +} diff --git a/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/SecretSharingTest.java b/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/SecretSharingTest.java new file mode 100644 index 0000000..021b529 --- /dev/null +++ b/distributed-key-generation/src/test/java/meerkat/crypto/secretsharing/shamir/SecretSharingTest.java @@ -0,0 +1,64 @@ +package meerkat.crypto.secretsharing.shamir; + +import meerkat.crypto.utils.concrete.Fp; +import meerkat.crypto.utils.GenerateRandomPrime; +import org.factcenter.qilin.primitives.CyclicGroup; +import org.factcenter.qilin.primitives.concrete.Zn; +import org.junit.Before; +import org.junit.Test; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * Created by Tzlil on 1/29/2016. + */ +public class SecretSharingTest { + + SecretSharing[] secretSharingArray; + BigInteger[] secrets; + CyclicGroup group; + int tests = 1 << 10; + Random random; + BigInteger p = GenerateRandomPrime.SafePrime100Bits; + BigInteger q = p.subtract(BigInteger.ONE).divide(BigInteger.valueOf(2)); + + @Before + public void settings(){ + group = new Zn(q); + int t = 9; + int n = 20; + random = new Random(); + secretSharingArray = new SecretSharing[tests]; + secrets = new BigInteger[tests]; + + for (int i = 0; i < secretSharingArray.length; i++){ + secrets[i] = group.sample(random); + secretSharingArray[i] = new SecretSharing(t,n,secrets[i],random,q); + } + } + + public void oneTest(SecretSharing secretSharing, BigInteger secret) throws Exception { + int t = secretSharing.getT(); + int n = secretSharing.getN(); + Polynomial.Point[] shares = new Polynomial.Point[t + 1]; + List indexes = new ArrayList(n); + for (int i = 1 ; i <= n; i ++){ + indexes.add(i); + } + for (int i = 0 ; i < shares.length ; i++){ + shares[i] = secretSharing.getShare(indexes.remove(random.nextInt(indexes.size()))); + } + BigInteger calculated = SecretSharing.recoverSecret(shares,new Fp(q)); + assert (secret.equals(calculated)); + } + + @Test + public void secretSharingTest() throws Exception { + for (int i = 0 ; i < secretSharingArray.length; i ++){ + oneTest(secretSharingArray[i],secrets[i]); + } + } +} diff --git a/distributed-key-generation/src/test/java/meerkat/crypto/utils/BigIntegerByteEncoder.java b/distributed-key-generation/src/test/java/meerkat/crypto/utils/BigIntegerByteEncoder.java new file mode 100644 index 0000000..2781abb --- /dev/null +++ b/distributed-key-generation/src/test/java/meerkat/crypto/utils/BigIntegerByteEncoder.java @@ -0,0 +1,28 @@ +package meerkat.crypto.utils; + +import java.math.BigInteger; + +/** + * Created by Tzlil on 4/7/2016. + */ +public class BigIntegerByteEncoder implements org.factcenter.qilin.util.ByteEncoder { + @Override + public byte[] encode(BigInteger input) { + return input.toByteArray(); + } + + @Override + public BigInteger decode(byte[] input) { + return new BigInteger(1,input); + } + + @Override + public int getMinLength() { + return 0; + } + + @Override + public BigInteger denseDecode(byte[] input) { + return decode(input); + } +} diff --git a/distributed-key-generation/src/test/java/meerkat/crypto/utils/GenerateRandomPolynomial.java b/distributed-key-generation/src/test/java/meerkat/crypto/utils/GenerateRandomPolynomial.java new file mode 100644 index 0000000..802c479 --- /dev/null +++ b/distributed-key-generation/src/test/java/meerkat/crypto/utils/GenerateRandomPolynomial.java @@ -0,0 +1,30 @@ +package meerkat.crypto.utils; + +import meerkat.crypto.secretsharing.shamir.Polynomial; +import meerkat.crypto.utils.concrete.Fp; + +import java.math.BigInteger; +import java.util.Random; + +/** + * Created by Tzlil on 1/27/2016. + */ +public class GenerateRandomPolynomial { + + public static Polynomial generateRandomPolynomial(int degree, int bits, Random random, Arithmetic arithmetic) { + BigInteger[] coefficients = new BigInteger[degree + 1]; + + for (int i = 0 ; i <= degree; i++ ){ + coefficients[i] = new BigInteger(bits,random); // sample from Zp [0,... q-1] + } + return new Polynomial(coefficients,arithmetic); + } + + public static Polynomial generateRandomPolynomial(int degree,int bits,Random random,BigInteger p) { + BigInteger[] coefficients = generateRandomPolynomial(degree,bits,random,new Fp(p)).getCoefficients(); + for (int i = 0; i { + @Override + public BigInteger add(BigInteger a, BigInteger b) { + return a.add(b); + } + + @Override + public BigInteger sub(BigInteger a, BigInteger b) { + return a.subtract(b); + } + + @Override + public BigInteger mul(BigInteger a, BigInteger b) { + return a.multiply(b); + } + + @Override + public BigInteger div(BigInteger a, BigInteger b) { + return a.divide(b); + } +} diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/AsyncBulletinBoardClient.java b/meerkat-common/src/main/java/meerkat/bulletinboard/AsyncBulletinBoardClient.java index aa23417..fcc6394 100644 --- a/meerkat-common/src/main/java/meerkat/bulletinboard/AsyncBulletinBoardClient.java +++ b/meerkat-common/src/main/java/meerkat/bulletinboard/AsyncBulletinBoardClient.java @@ -13,6 +13,7 @@ public interface AsyncBulletinBoardClient extends BulletinBoardClient { /** * Post a message to the bulletin board in an asynchronous manner + * The message may be broken up by the client into a batch message, depending on implementation * @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 @@ -51,30 +52,30 @@ public interface AsyncBulletinBoardClient extends BulletinBoardClient { * It does not close the batch * @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 portion of the batch to be posted + * @param batchChunkList is the (canonically ordered) list of data comprising the portion of the batch to be posted * @param startPosition is the location (in the batch) of the first entry in batchDataList * (optionally used to continue interrupted post operations) * The first position in the batch is position 0 * @param callback is a callback function class for handling results of the operation */ - public void postBatchData(byte[] signerId, int batchId, List batchDataList, + public void postBatchData(byte[] signerId, int batchId, List batchChunkList, int startPosition, FutureCallback callback); /** * Overloading of the postBatchData method which starts at the first position in the batch */ - public void postBatchData(byte[] signerId, int batchId, List batchDataList, FutureCallback callback); + public void postBatchData(byte[] signerId, int batchId, List batchChunkList, FutureCallback callback); /** * Overloading of the postBatchData method which uses ByteString */ - public void postBatchData(ByteString signerId, int batchId, List batchDataList, + public void postBatchData(ByteString signerId, int batchId, List batchChunkList, int startPosition, FutureCallback callback); /** * Overloading of the postBatchData method which uses ByteString and starts at the first position in the batch */ - public void postBatchData(ByteString signerId, int batchId, List batchDataList, FutureCallback callback); + public void postBatchData(ByteString signerId, int batchId, List batchChunkList, FutureCallback callback); /** * Attempts to close a batch message @@ -96,6 +97,7 @@ public interface AsyncBulletinBoardClient extends BulletinBoardClient { * 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. + * Also: batch messages are returned as stubs. * @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 */ @@ -103,10 +105,10 @@ public interface AsyncBulletinBoardClient extends BulletinBoardClient { /** * Read a given batch message from the bulletin board - * @param batchSpecificationMessage contains the data required to specify a single batch instance + * @param msgID is the batch message ID to be read * @param callback is a callback class for handling the result of the operation */ - public void readBatch(BatchSpecificationMessage batchSpecificationMessage, FutureCallback callback); + public void readBatch(MessageID msgID, FutureCallback callback); /** diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/BatchDigitalSignature.java b/meerkat-common/src/main/java/meerkat/bulletinboard/BatchDigitalSignature.java deleted file mode 100644 index e04f3c5..0000000 --- a/meerkat-common/src/main/java/meerkat/bulletinboard/BatchDigitalSignature.java +++ /dev/null @@ -1,34 +0,0 @@ -package meerkat.bulletinboard; - -import meerkat.crypto.DigitalSignature; -import meerkat.protobuf.BulletinBoardAPI.BeginBatchMessage; -import meerkat.protobuf.BulletinBoardAPI.BatchData; -import meerkat.protobuf.Crypto.Signature; - -import java.security.InvalidKeyException; -import java.security.SignatureException; -import java.security.cert.CertificateException; -import java.util.List; - -/** - * Created by Arbel Deutsch Peled on 20-Dec-15. - * Extends the DigitalSignature interface with methods for signing and authenticating Batch messages - */ -public interface BatchDigitalSignature extends DigitalSignature { - - /** - * Appends the batch data to the signed content (ignoring the signature) - * @param completeBatch contains all the data about the batch - * @throws SignatureException - */ - public void updateContent(CompleteBatch completeBatch) throws SignatureException; - - /** - * Performs a complete verification process on the given batch message - * @param completeBatch contains the batch data as well as the signature - * @return TRUE if the batch is verified and FALSE otherwise - * @throws SignatureException | SignatureException | InvalidKeyException when underlying methods do so - */ - public boolean verify(CompleteBatch completeBatch) throws SignatureException, CertificateException, InvalidKeyException; - -} diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardClient.java b/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardClient.java index 4fb526a..9ce3943 100644 --- a/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardClient.java +++ b/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardClient.java @@ -5,7 +5,6 @@ import meerkat.protobuf.Voting.*; import static meerkat.protobuf.BulletinBoardAPI.*; -import java.util.Collection; import java.util.List; /** @@ -21,20 +20,13 @@ public interface BulletinBoardClient { /** * Post a message to the bulletin board in a synchronous manner + * The message may be broken up by the client into a batch message depending on implementation * @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; - /** - * Perform an end-to-end post of a signed batch message - * @param completeBatch contains all the data of the batch including the meta-data and the signature - * @return a unique identifier for the batch message - * @throws CommunicationException - */ - public MessageID postBatch(CompleteBatch completeBatch) throws CommunicationException; - /** * Check how "safe" a given message is in a synchronous manner * @param id is the unique message identifier for retrieval @@ -47,6 +39,7 @@ public interface BulletinBoardClient { * 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. + * Also: batch messages are returned as stubs. * @param filterList return only messages that match the filters (null means no filtering) * @return the list of messages */ @@ -54,11 +47,11 @@ public interface BulletinBoardClient { /** * Read a given batch message from the bulletin board - * @param batchSpecificationMessage contains the data required to specify a single batch instance + * @param msgID is the batch message ID to be read * @return the complete batch * @throws CommunicationException */ - CompleteBatch readBatch(BatchSpecificationMessage batchSpecificationMessage) throws CommunicationException; + BulletinBoardMessage readBatch(MessageID msgID) throws CommunicationException; /** * Create a SyncQuery to test against that corresponds with the current server state for a specific filter list diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardConstants.java b/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardConstants.java index f2fb3a9..9404165 100644 --- a/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardConstants.java +++ b/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardConstants.java @@ -18,9 +18,4 @@ public interface BulletinBoardConstants { public static final String CLOSE_BATCH_PATH = "/closebatch"; public static final String SYNC_QUERY_PATH = "/syncquery"; - // Other Constants - - public static final String BATCH_TAG = "@BATCH"; - public static final String BATCH_ID_TAG_PREFIX = "BATCHID#"; - } diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardDigest.java b/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardDigest.java index 0521d73..3e47c23 100644 --- a/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardDigest.java +++ b/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardDigest.java @@ -7,36 +7,24 @@ import java.util.List; /** * Created by Arbel Deutsch Peled on 18-Dec-15. - * Extends the Digest interface with a method for digesting Batch messages + * Extends the Digest interface with methods for digesting Bulletin Board messages */ public interface BulletinBoardDigest extends Digest { /** - * Update the digest with the batch message data (ignore the signature) + * Update the digest with the message data (ignore the signature) * The digest only uses the part the signatures are computed on for this operation - * @param completeBatch is the batch message that needs to be digested + * If the message is a stub: this should be called before digesting the raw data + * @param msg is the message that needs to be digested */ - public void updateCompleteBatch(BulletinBoardMessage completeBatch); + public void update(BulletinBoardMessage msg); /** - * Update the digest with a BulletinBoardMessage that contains only the metadata of a batch message + * Update the digest with the message data (ignore the signature) * The digest only uses the part the signatures are computed on for this operation - * This operation is necessary before beginning to digest the actual data - * @param batchStub contains the metadata + * If the message is a stub: this should be called before digesting the raw data + * @param msg is the message that needs to be digested */ - public void updateBatchStub(BulletinBoardMessage batchStub); - - /** - * Update the digest with the batch message data (ignore the signature) - * @param completeBatch is the batch message that needs to be digested - */ - public void updateCompleteBatch(UnsignedBulletinBoardMessage completeBatch); - - /** - * Update the digest with a BulletinBoardMessage that contains only the metadata of a batch message - * This operation is necessary before beginning to digest the actual data - * @param batchStub contains the metadata - */ - public void updateBatchStub(UnsignedBulletinBoardMessage batchStub); + public void update(UnsignedBulletinBoardMessage msg); } diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardServer.java b/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardServer.java index e26c54a..3d33704 100644 --- a/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardServer.java +++ b/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardServer.java @@ -1,11 +1,11 @@ package meerkat.bulletinboard; +import com.google.protobuf.BoolValue; +import com.google.protobuf.Int32Value; import meerkat.comm.CommunicationException; import meerkat.comm.MessageOutputStream; import meerkat.protobuf.BulletinBoardAPI.*; -import java.util.Collection; - /** * Created by Arbel on 07/11/15. @@ -29,7 +29,7 @@ public interface BulletinBoardServer{ * @return TRUE if the message has been authenticated and FALSE otherwise (in ProtoBuf form) * @throws CommunicationException on DB connection error */ - public BoolMsg postMessage(BulletinBoardMessage msg) throws CommunicationException; + public BoolValue postMessage(BulletinBoardMessage msg) throws CommunicationException; /** * Read all posted messages matching the given filters @@ -45,7 +45,7 @@ public interface BulletinBoardServer{ * @return an IntMsg containing the number of messages that match the filter * @throws CommunicationException on DB connection error */ - public IntMsg getMessageCount(MessageFilterList filterList) throws CommunicationException; + public Int32Value getMessageCount(MessageFilterList filterList) throws CommunicationException; /** * Informs server about a new batch message @@ -55,10 +55,10 @@ public interface BulletinBoardServer{ * However, if such a batch exists and is already closed: the value returned will be FALSE * @throws CommunicationException on DB connection error */ - public BoolMsg beginBatch(BeginBatchMessage message) throws CommunicationException; + public BoolValue beginBatch(BeginBatchMessage message) throws CommunicationException; /** - * Posts a (part of a) batch message to the bulletin board + * Posts a chunk of a batch message to the bulletin board * Note that the existence and contents of a batch message are not available for reading before the batch is finalized * @param batchMessage contains the (partial) data this message carries as well as meta-data required in order to place the data * in the correct position inside the correct batch @@ -66,8 +66,8 @@ public interface BulletinBoardServer{ * Specifically, if the batch is already closed: the value returned will be FALSE * However, requiring to open a batch before insertion of messages is implementation-dependent * @throws CommunicationException on DB connection error - */ - public BoolMsg postBatchMessage(BatchMessage batchMessage) throws CommunicationException; + */ + public BoolValue postBatchMessage(BatchMessage batchMessage) throws CommunicationException; /** * Attempts to close and finalize a batch message @@ -76,16 +76,16 @@ public interface BulletinBoardServer{ * Specifically, if the signature is invalid or if some of the batch parts have not yet been submitted: the value returned will be FALSE * @throws CommunicationException on DB connection error */ - public BoolMsg closeBatchMessage(CloseBatchMessage message) throws CommunicationException; + public BoolValue closeBatch(CloseBatchMessage message) throws CommunicationException; /** * Reads a batch message from the server (starting with the supplied position) - * @param message specifies the signer ID and the batch ID to read as well as an (optional) start position + * @param batchQuery specifies which batch and what parts of it to retrieve * @param out is a stream of the ordered batch messages starting from the specified start position (if given) or from the beginning (if omitted) * @throws CommunicationException on DB connection error - * @throws IllegalArgumentException if message does not specify a batch + * @throws IllegalArgumentException if message ID does not specify a batch */ - public void readBatch(BatchSpecificationMessage message, MessageOutputStream out) throws CommunicationException, IllegalArgumentException; + public void readBatch(BatchQuery batchQuery, MessageOutputStream out) throws CommunicationException, IllegalArgumentException; /** * Create a SyncQuery to test against that corresponds with the current server state for a specific filter list diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardSignature.java b/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardSignature.java new file mode 100644 index 0000000..ba018ca --- /dev/null +++ b/meerkat-common/src/main/java/meerkat/bulletinboard/BulletinBoardSignature.java @@ -0,0 +1,31 @@ +package meerkat.bulletinboard; + +import meerkat.crypto.DigitalSignature; +import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage; +import meerkat.protobuf.BulletinBoardAPI.UnsignedBulletinBoardMessage; +import meerkat.protobuf.Crypto; + +import java.security.SignatureException; + +/** + * Created by Arbel Deutsch Peled on 18-Dec-15. + * Extends the DigitalSignature interface with methods for signing Bulletin Board messages + */ +public interface BulletinBoardSignature extends DigitalSignature { + + /** + * Add msg to the content stream to be verified / signed + * The digest only uses the part the signatures are computed on for this operation + * If the message is a stub: this should be called before updating with the raw data + * @param msg is the message that needs to be digested + */ + public void updateContent(BulletinBoardMessage msg) throws SignatureException; + + /** + * Add msg to the content stream to be verified / signed + * If the message is a stub: this should be called before updating with the raw data + * @param msg is the message that needs to be digested + */ + public void updateContent(UnsignedBulletinBoardMessage msg) throws SignatureException; + +} diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/CompleteBatch.java b/meerkat-common/src/main/java/meerkat/bulletinboard/CompleteBatch.java deleted file mode 100644 index 89ae80a..0000000 --- a/meerkat-common/src/main/java/meerkat/bulletinboard/CompleteBatch.java +++ /dev/null @@ -1,181 +0,0 @@ -package meerkat.bulletinboard; - -import com.google.protobuf.Timestamp; -import meerkat.protobuf.BulletinBoardAPI.*; -import meerkat.protobuf.Crypto.*; -import meerkat.util.BulletinBoardUtils; - -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; - -/** - * Created by Arbel Deutsch Peled on 14-Dec-15. - * - * A data structure for holding a complete batch message along with its signature - */ -public class CompleteBatch { - - private BeginBatchMessage beginBatchMessage; - private List batchDataList; - private Signature signature; - private Timestamp timestamp; - - public CompleteBatch() { - batchDataList = new LinkedList(); - } - - public CompleteBatch(BeginBatchMessage newBeginBatchMessage) { - this(); - beginBatchMessage = newBeginBatchMessage; - } - - public CompleteBatch(BeginBatchMessage newBeginBatchMessage, List newDataList) { - this(newBeginBatchMessage); - appendBatchData(newDataList); - } - - public CompleteBatch(BeginBatchMessage newBeginBatchMessage, List newDataList, Signature newSignature) { - this(newBeginBatchMessage, newDataList); - signature = newSignature; - } - - public CompleteBatch(BeginBatchMessage newBeginBatchMessage, List newDataList, Signature newSignature, Timestamp timestamp) { - this(newBeginBatchMessage, newDataList, newSignature); - this.timestamp = timestamp; - } - - public CompleteBatch(Timestamp timestamp) { - this(); - this.timestamp = timestamp; - } - - /** - * Combines the actual Bulletin board representation of a batch into a CompleteBatch object - * @param batchMessage is the BulletinBoard message that specifies the batch - * @param batchDataList is the (ordered) list of batch data - */ - public CompleteBatch(BulletinBoardMessage batchMessage, List batchDataList) throws IllegalArgumentException{ - - final String[] PREFIXES = { - BulletinBoardConstants.BATCH_ID_TAG_PREFIX, - BulletinBoardConstants.BATCH_TAG}; - - String batchIdStr = BulletinBoardUtils.findTagWithPrefix(batchMessage, BulletinBoardConstants.BATCH_ID_TAG_PREFIX); - - if (batchIdStr == null){ - throw new IllegalArgumentException(""); - } - - this.beginBatchMessage = - BeginBatchMessage.newBuilder() - .setSignerId(batchMessage.getSig(0).getSignerId()) - .setBatchId(Integer.parseInt(batchIdStr)) - .addAllTag(BulletinBoardUtils.removePrefixTags(batchMessage, Arrays.asList(PREFIXES))) - .build(); - - this.batchDataList = batchDataList; - this.signature = batchMessage.getSig(0); - this.timestamp = batchMessage.getMsg().getTimestamp(); - - } - - public BeginBatchMessage getBeginBatchMessage() { - return beginBatchMessage; - } - - public List getBatchDataList() { - return batchDataList; - } - - public Signature getSignature() { - return signature; - } - - public Timestamp getTimestamp() { - return timestamp; - } - - public CloseBatchMessage getCloseBatchMessage() { - return CloseBatchMessage.newBuilder() - .setBatchId(getBeginBatchMessage().getBatchId()) - .setBatchLength(getBatchDataList().size()) - .setSig(getSignature()) - .setTimestamp(getTimestamp()) - .build(); - } - - public void setBeginBatchMessage(BeginBatchMessage beginBatchMessage) { - this.beginBatchMessage = beginBatchMessage; - } - - public void appendBatchData(BatchData newBatchData) { - batchDataList.add(newBatchData); - } - - public void appendBatchData(List newBatchDataList) { - batchDataList.addAll(newBatchDataList); - } - - public void setSignature(Signature newSignature) { - signature = newSignature; - } - - public void setTimestamp(Timestamp timestamp) { - this.timestamp = timestamp; - } - - @Override - public boolean equals(Object other) { - - if (!(other instanceof CompleteBatch)) { - return false; - } - - CompleteBatch otherBatch = (CompleteBatch) other; - - boolean result = true; - - if (beginBatchMessage == null) { - if (otherBatch.getBeginBatchMessage() != null) - return false; - } else { - result = result && beginBatchMessage.equals(otherBatch.getBeginBatchMessage()); - } - - if (batchDataList == null) { - if (otherBatch.getBatchDataList() != null) - return false; - } else { - result = result && batchDataList.equals(otherBatch.getBatchDataList()); - } - - if (signature == null) { - if (otherBatch.getSignature() != null) - return false; - } else { - result = result && signature.equals(otherBatch.getSignature()); - } - - if (timestamp == null) { - if (otherBatch.getTimestamp() != null) - return false; - } else { - result = result && timestamp.equals(otherBatch.getTimestamp()); - } - - return result; - - } - - @Override - public String toString() { - - if (beginBatchMessage == null || beginBatchMessage.getSignerId() == null) - return "Unspecified batch " + super.toString(); - - return "Batch " + beginBatchMessage.getSignerId().toString() + ":" + beginBatchMessage.getBatchId(); - - } - -} diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/DeletableBulletinBoardServer.java b/meerkat-common/src/main/java/meerkat/bulletinboard/DeletableBulletinBoardServer.java index 03e0c3f..cfff084 100644 --- a/meerkat-common/src/main/java/meerkat/bulletinboard/DeletableBulletinBoardServer.java +++ b/meerkat-common/src/main/java/meerkat/bulletinboard/DeletableBulletinBoardServer.java @@ -1,5 +1,6 @@ package meerkat.bulletinboard; +import com.google.protobuf.BoolValue; import meerkat.comm.CommunicationException; import meerkat.protobuf.BulletinBoardAPI.*; @@ -10,20 +11,22 @@ public interface DeletableBulletinBoardServer extends BulletinBoardServer { /** * Deletes a message from the Bulletin Board + * If the message is a batch: the batch data is deleted as well * Logs this action * @param msgID is the ID of the message to delete * @return a BoolMsg containing the value TRUE if a message was deleted, FALSE if the message does not exist * @throws CommunicationException in case of an error */ - public BoolMsg deleteMessage(MessageID msgID) throws CommunicationException; + public BoolValue deleteMessage(MessageID msgID) throws CommunicationException; /** * Deletes a message from the Bulletin Board + * If the message is a batch: the batch data is deleted as well * Logs this action * @param entryNum is the serial entry number of the message to delete * @return a BoolMsg containing the value TRUE if a message was deleted, FALSE if the message does not exist * @throws CommunicationException in case of an error */ - public BoolMsg deleteMessage(long entryNum) throws CommunicationException; + public BoolValue deleteMessage(long entryNum) throws CommunicationException; } diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/GenericBatchDigitalSignature.java b/meerkat-common/src/main/java/meerkat/bulletinboard/GenericBatchDigitalSignature.java deleted file mode 100644 index 7327b8e..0000000 --- a/meerkat-common/src/main/java/meerkat/bulletinboard/GenericBatchDigitalSignature.java +++ /dev/null @@ -1,104 +0,0 @@ -package meerkat.bulletinboard; - -import com.google.protobuf.ByteString; -import com.google.protobuf.Message; -import meerkat.crypto.DigitalSignature; -import meerkat.protobuf.BulletinBoardAPI.BatchData; -import meerkat.protobuf.Crypto; - -import java.io.IOException; -import java.io.InputStream; -import java.security.InvalidKeyException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.SignatureException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; - -/** - * Created by Arbel Deutsch Peled on 20-Dec-15. - * Wrapper class for signing and verifying Batch signatures in a standardized way - */ -public class GenericBatchDigitalSignature implements BatchDigitalSignature{ - - private DigitalSignature digitalSignature; - - public GenericBatchDigitalSignature(DigitalSignature digitalSignature) { - this.digitalSignature = digitalSignature; - } - - @Override - public void updateContent(CompleteBatch completeBatch) throws SignatureException { - - digitalSignature.updateContent(completeBatch.getBeginBatchMessage()); - - for (BatchData batchData : completeBatch.getBatchDataList()) { - digitalSignature.updateContent(batchData); - } - - digitalSignature.updateContent(completeBatch.getTimestamp()); - - } - - @Override - public boolean verify(CompleteBatch completeBatch) throws SignatureException, CertificateException, InvalidKeyException { - - digitalSignature.initVerify(completeBatch.getSignature()); - updateContent(completeBatch); - return digitalSignature.verify(); - - } - - @Override - public void loadVerificationCertificates(InputStream certStream) throws CertificateException { - digitalSignature.loadVerificationCertificates(certStream); - } - - @Override - public void clearVerificationCertificates() { - digitalSignature.clearVerificationCertificates(); - } - - @Override - public void updateContent(Message msg) throws SignatureException { - digitalSignature.updateContent(msg); - } - - @Override - public Crypto.Signature sign() throws SignatureException { - return digitalSignature.sign(); - } - - @Override - public void initVerify(Crypto.Signature sig) throws CertificateException, InvalidKeyException { - digitalSignature.initVerify(sig); - } - - @Override - public boolean verify() { - return digitalSignature.verify(); - } - - @Override - public KeyStore.Builder getPKCS12KeyStoreBuilder(InputStream keyStream, char[] password) - throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException { - return digitalSignature.getPKCS12KeyStoreBuilder(keyStream, password); - } - - @Override - public void loadSigningCertificate(KeyStore.Builder keyStoreBuilder) throws IOException, CertificateException, UnrecoverableKeyException { - digitalSignature.loadSigningCertificate(keyStoreBuilder); - } - - @Override - public ByteString getSignerID() { - return digitalSignature.getSignerID(); - } - - @Override - public void clearSigningKey() { - digitalSignature.clearSigningKey(); - } - -} diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/GenericBulletinBoardDigest.java b/meerkat-common/src/main/java/meerkat/bulletinboard/GenericBulletinBoardDigest.java index cc912a7..9930ab3 100644 --- a/meerkat-common/src/main/java/meerkat/bulletinboard/GenericBulletinBoardDigest.java +++ b/meerkat-common/src/main/java/meerkat/bulletinboard/GenericBulletinBoardDigest.java @@ -1,5 +1,6 @@ package meerkat.bulletinboard; +import com.google.protobuf.ByteString; import com.google.protobuf.BytesValue; import com.google.protobuf.Message; import meerkat.crypto.Digest; @@ -51,29 +52,23 @@ public class GenericBulletinBoardDigest implements BulletinBoardDigest { } @Override - public void updateCompleteBatch(BulletinBoardMessage completeBatch) { - updateCompleteBatch(completeBatch.getMsg()); + public void update(BulletinBoardMessage msg) { + update(msg.getMsg()); } @Override - public void updateBatchStub(BulletinBoardMessage batchStub) { - updateBatchStub(batchStub.getMsg()); - } + public void update(UnsignedBulletinBoardMessage msg) { - @Override - public void updateCompleteBatch(UnsignedBulletinBoardMessage completeBatch) { + for (ByteString tag : msg.getTagList().asByteStringList()){ + update(tag.toByteArray()); + } - // Digest just the signed part - UnsignedBulletinBoardMessage batchStub = completeBatch.toBuilder().clearData().build(); - updateBatchStub(batchStub); + update(msg.getTimestamp()); - // Digest the data - update(completeBatch.getData().toByteArray()); + if (!msg.getIsStub()){ + update(msg.getData().toByteArray()); + } } - @Override - public void updateBatchStub(UnsignedBulletinBoardMessage batchStub) { - update(batchStub); - } } diff --git a/meerkat-common/src/main/java/meerkat/bulletinboard/GenericBulletinBoardSignature.java b/meerkat-common/src/main/java/meerkat/bulletinboard/GenericBulletinBoardSignature.java new file mode 100644 index 0000000..e818071 --- /dev/null +++ b/meerkat-common/src/main/java/meerkat/bulletinboard/GenericBulletinBoardSignature.java @@ -0,0 +1,106 @@ +package meerkat.bulletinboard; + +import com.google.protobuf.ByteString; +import com.google.protobuf.Message; +import meerkat.crypto.Digest; +import meerkat.crypto.DigitalSignature; +import meerkat.protobuf.BulletinBoardAPI; +import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage; +import meerkat.protobuf.BulletinBoardAPI.MessageID; +import meerkat.protobuf.BulletinBoardAPI.UnsignedBulletinBoardMessage; +import meerkat.protobuf.Crypto; + +import java.io.IOException; +import java.io.InputStream; +import java.security.*; +import java.security.cert.CertificateException; + + +/** + * Created by Arbel Deutsch Peled on 19-Dec-15. + * Wrapper class for digesting Batches in a standardized way + */ +public class GenericBulletinBoardSignature implements BulletinBoardSignature { + + private DigitalSignature signer; + + public GenericBulletinBoardSignature(DigitalSignature signer) { + this.signer = signer; + } + + @Override + public void updateContent(BulletinBoardMessage msg) throws SignatureException{ + signer.updateContent(msg.getMsg()); + } + + @Override + public void updateContent(UnsignedBulletinBoardMessage msg) throws SignatureException{ + + for (ByteString tag : msg.getTagList().asByteStringList()){ + updateContent(tag.toByteArray()); + } + + updateContent(msg.getTimestamp()); + + if (!msg.getIsStub()){ + updateContent(msg.getData().toByteArray()); + } + + } + + @Override + public void loadVerificationCertificates(InputStream certStream) throws CertificateException { + signer.loadVerificationCertificates(certStream); + } + + @Override + public void clearVerificationCertificates() { + signer.clearVerificationCertificates(); + } + + @Override + public void updateContent(byte[] data) throws SignatureException { + signer.updateContent(data); + } + + @Override + public void updateContent(Message msg) throws SignatureException { + signer.updateContent(msg); + } + + @Override + public Crypto.Signature sign() throws SignatureException { + return signer.sign(); + } + + @Override + public void initVerify(Crypto.Signature sig) throws CertificateException, InvalidKeyException { + signer.initVerify(sig); + } + + @Override + public boolean verify() { + return signer.verify(); + } + + @Override + public KeyStore.Builder getPKCS12KeyStoreBuilder(InputStream keyStream, char[] password) throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException { + return signer.getPKCS12KeyStoreBuilder(keyStream, password); + } + + @Override + public void loadSigningCertificate(KeyStore.Builder keyStoreBuilder) throws IOException, CertificateException, UnrecoverableKeyException { + signer.loadSigningCertificate(keyStoreBuilder); + } + + @Override + public ByteString getSignerID() { + return signer.getSignerID(); + } + + @Override + public void clearSigningKey() { + signer.clearSigningKey(); + } + +} diff --git a/meerkat-common/src/main/java/meerkat/comm/Channel.java b/meerkat-common/src/main/java/meerkat/comm/Channel.java new file mode 100644 index 0000000..7dd74c1 --- /dev/null +++ b/meerkat-common/src/main/java/meerkat/comm/Channel.java @@ -0,0 +1,42 @@ +package meerkat.comm; + +import com.google.protobuf.Message; +import meerkat.protobuf.Comm; + +/** + * A generic communication channel that supports point-to-point and broadcast operation + */ + +public interface Channel { + /** + * Return the id of the channel's endpoint (this will be used as the source of message sent from the channel). + * @return + */ + public int getSourceId(); + + public interface ReceiverCallback { + public void receiveMessage(Comm.BroadcastMessage envelope); + } + + /** + * sends a private message + * @param destUser destination user's identifier + * @param msg message + */ + public void sendMessage(int destUser, Message msg); + + + /** + * broadcasts a message to all parties (including the sender) + * @param msg message + */ + public void broadcastMessage(Message msg); + + /** + * Register a callback to handle received messages. + * The callback is called in the Channel thread, so no long processing should + * occur in the callback method. + * @param callback + */ + public void registerReceiverCallback(ReceiverCallback callback); +} diff --git a/meerkat-common/src/main/java/meerkat/crypto/DigitalSignature.java b/meerkat-common/src/main/java/meerkat/crypto/DigitalSignature.java index 76c32a6..707e500 100644 --- a/meerkat-common/src/main/java/meerkat/crypto/DigitalSignature.java +++ b/meerkat-common/src/main/java/meerkat/crypto/DigitalSignature.java @@ -39,6 +39,13 @@ public interface DigitalSignature { */ public void clearVerificationCertificates(); + /** + * Add raw data to the content stream to be verified / signed. + * + * @param data + * @throws SignatureException + */ + public void updateContent(byte[] data) throws SignatureException; /** * Add msg to the content stream to be verified / signed. Each message is (automatically) diff --git a/meerkat-common/src/main/java/meerkat/crypto/concrete/ECDSASignature.java b/meerkat-common/src/main/java/meerkat/crypto/concrete/ECDSASignature.java index ab8084b..c51f2f2 100644 --- a/meerkat-common/src/main/java/meerkat/crypto/concrete/ECDSASignature.java +++ b/meerkat-common/src/main/java/meerkat/crypto/concrete/ECDSASignature.java @@ -140,6 +140,11 @@ public class ECDSASignature implements DigitalSignature { signer.update(msg.toByteString().asReadOnlyByteBuffer()); } + @Override + public void updateContent(byte[] data) throws SignatureException { + signer.update(data); + } + public void updateContent(InputStream in) throws IOException, SignatureException { ByteString inStr = ByteString.readFrom(in); signer.update(inStr.asReadOnlyByteBuffer()); diff --git a/meerkat-common/src/main/java/meerkat/crypto/concrete/ECElGamalEncryption.java b/meerkat-common/src/main/java/meerkat/crypto/concrete/ECElGamalEncryption.java index 98d32bf..ab14421 100644 --- a/meerkat-common/src/main/java/meerkat/crypto/concrete/ECElGamalEncryption.java +++ b/meerkat-common/src/main/java/meerkat/crypto/concrete/ECElGamalEncryption.java @@ -113,7 +113,7 @@ public class ECElGamalEncryption implements Encryption { Pair randomizer = elGamalPK.encrypt(curve.getInfinity(), rndInt); ConcreteCrypto.ElGamalCiphertext originalEncodedCipher= ConcreteCrypto.ElGamalCiphertext.parseFrom(msg.getData()); - Pair originalCipher = new Pair<>( + Pair originalCipher = new Pair( curve.decodePoint(originalEncodedCipher.getC1().toByteArray()), curve.decodePoint(originalEncodedCipher.getC2().toByteArray())); Pair newCipher = elGamalPK.add(originalCipher, randomizer); diff --git a/meerkat-common/src/main/java/meerkat/pollingstation/PollingStationConstants.java b/meerkat-common/src/main/java/meerkat/pollingstation/PollingStationConstants.java new file mode 100644 index 0000000..1bc6d3c --- /dev/null +++ b/meerkat-common/src/main/java/meerkat/pollingstation/PollingStationConstants.java @@ -0,0 +1,13 @@ +package meerkat.pollingstation; + +/** + * Created by Arbel Deutsch Peled on 21-Dec-15. + */ +public interface PollingStationConstants { + + // Relative addresses for Scanner operations + + public static final String POLLING_STATION_WEB_SCANNER_SCAN_PATH = "/scan"; + public static final String POLLING_STATION_WEB_SCANNER_ERROR_PATH = "/error"; + +} diff --git a/meerkat-common/src/main/java/meerkat/pollingstation/PollingStationScanner.java b/meerkat-common/src/main/java/meerkat/pollingstation/PollingStationScanner.java new file mode 100644 index 0000000..290d0ee --- /dev/null +++ b/meerkat-common/src/main/java/meerkat/pollingstation/PollingStationScanner.java @@ -0,0 +1,61 @@ +package meerkat.pollingstation; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.protobuf.BoolValue; +import meerkat.protobuf.PollingStation.*; +/** + * Created by Arbel on 05/05/2016. + * An interface for the scanner used by the Polling Station Committee + * The scanner works as a producer, while the polling station is the consumer + * That is to say: scans are pushed from the scanner rather than requested by the polling station + */ +public interface PollingStationScanner { + + /** + * An interface for processing scans (Polling Station side) + */ + public interface Consumer { + + /** + * Sets up the connection to the scanner and begins receiving scans + * @throws Exception when the operation fails + */ + public void start() throws Exception; + + /** + * Closes the connection to the scanner + * @throws Exception when the operation fails + */ + public void stop() throws Exception; + + /** + * Subscribes to new scans + * + * @param scanCallback is the handler for scanned data + */ + public void subscribe(FutureCallback scanCallback); + + } + + /** + * An interface for submitting scanned data (scanner side) + */ + public interface Producer { + + /** + * Sends a scan to all subscribers + * @param scannedData contains the scanned data + * @return a BoolValue containing TRUE iff the scanned data has been sent to at least one subscriber + */ + public BoolValue newScan(ScannedData scannedData); + + /** + * Notifies subscribers about an error that occurred during scan + * @param errorMsg is the error that occurred + * @return a BoolValue containing TRUE iff the error has been sent to at least one subscriber + */ + public BoolValue reportScanError(ErrorMsg errorMsg); + + } + +} \ No newline at end of file diff --git a/meerkat-common/src/main/java/meerkat/util/BulletinBoardMessageComparator.java b/meerkat-common/src/main/java/meerkat/util/BulletinBoardMessageComparator.java index 77a6663..ec9b2e0 100644 --- a/meerkat-common/src/main/java/meerkat/util/BulletinBoardMessageComparator.java +++ b/meerkat-common/src/main/java/meerkat/util/BulletinBoardMessageComparator.java @@ -5,6 +5,7 @@ import meerkat.protobuf.BulletinBoardAPI.*; import meerkat.protobuf.Crypto.*; import java.util.Comparator; +import java.util.Iterator; import java.util.List; /** @@ -24,22 +25,51 @@ public class BulletinBoardMessageComparator implements Comparator msg1Sigs = msg1.getSigList(); - List msg2Sigs = msg2.getSigList(); - // Compare unsigned message - if (!msg1.getMsg().equals(msg2.getMsg())){ + + // Compare Timestamps + + if (!msg1.getMsg().getTimestamp().equals(msg2.getMsg().getTimestamp())){ return -1; } - // Compare signatures + // Compare tags (enforce order) - if (msg1Sigs.size() != msg2Sigs.size()){ + List tags1 = msg1.getMsg().getTagList(); + Iterator tags2 = msg2.getMsg().getTagList().iterator(); + + for (String tag : tags1){ + if (!tags2.hasNext()) { + return -1; + } + if (!tags2.next().equals(tag)){ + return -1; + } + } + + // Compare data (only for non-stub messages) + + if (msg1.getMsg().getIsStub() != msg2.getMsg().getIsStub()){ return -1; } - for (Signature sig : msg1Sigs){ - if (!msg2Sigs.contains(sig)) { + if (msg1.getMsg().getIsStub()){ + if (!msg1.getMsg().getData().equals(msg2.getMsg().getData())){ + return -1; + } + } + + // Compare signatures (do not enforce order) + + List sigs1 = msg1.getSigList(); + List sigs2 = msg2.getSigList(); + + if (sigs1.size() != sigs2.size()){ + return -1; + } + + for (Signature sig : sigs1){ + if (!sigs2.contains(sig)) { return -1; } } diff --git a/meerkat-common/src/main/java/meerkat/util/BulletinBoardMessageGenerator.java b/meerkat-common/src/main/java/meerkat/util/BulletinBoardMessageGenerator.java index ea25200..5969a1e 100644 --- a/meerkat-common/src/main/java/meerkat/util/BulletinBoardMessageGenerator.java +++ b/meerkat-common/src/main/java/meerkat/util/BulletinBoardMessageGenerator.java @@ -1,8 +1,6 @@ package meerkat.util; import com.google.protobuf.ByteString; -import meerkat.bulletinboard.BatchDigitalSignature; -import meerkat.bulletinboard.CompleteBatch; import meerkat.crypto.DigitalSignature; import meerkat.protobuf.BulletinBoardAPI.*; import com.google.protobuf.Timestamp; @@ -131,97 +129,4 @@ public class BulletinBoardMessageGenerator { } - /** - * Generates a complete instance of a CompleteBatch - * @param signer contains the credentials required to sign the message - * @param batchId is the (per-signer) batch-ID - * @param timestamp is the time at which the message was generated - * @param dataCount is the number of Batch Data in the batch - * @param dataSize is the number of bytes per Batch Data - * @param tagCount is the number of tags - * @param tags contains a list of tags to be added (in addition to the random ones) - * @return a random, signed CompleteBatch containing random data and tags - * @throws SignatureException if an error occurs while signing the batch - */ - public CompleteBatch generateRandomBatch(BatchDigitalSignature signer, int batchId, Timestamp timestamp, int dataCount, int dataSize, int tagCount, List tags) throws SignatureException { - - CompleteBatch result = new CompleteBatch(BeginBatchMessage.newBuilder() - .setSignerId(signer.getSignerID()) - .setBatchId(batchId) - .addAllTag(tags) - .addAllTag(randomStrings(tagCount)) - .build()); - - List batchDataList = new LinkedList<>(); - - for (int i = 0 ; i < dataCount ; i++) { - batchDataList.add(BatchData.newBuilder() - .setData(ByteString.copyFrom(randomBytes(dataSize))) - .build()); - } - - result.appendBatchData(batchDataList); - - result.setTimestamp(timestamp); - - signer.updateContent(result); - - result.setSignature(signer.sign()); - - return result; - - } - - /** - * Generates a complete instance of a CompleteBatch - * @param signer contains the credentials required to sign the message - * @param batchId is the (per-signer) batch-ID - * @param timestamp is the time at which the message was generated - * @param dataCount is the number of Batch Data in the batch - * @param dataSize is the number of bytes per Batch Data - * @param tagCount is the number of tags - * @return a random, signed CompleteBatch containing random data and tags - * @throws SignatureException if an error occurs while signing the batch - */ - public CompleteBatch generateRandomBatch(BatchDigitalSignature signer, int batchId, Timestamp timestamp, int dataCount, int dataSize, int tagCount) throws SignatureException { - return generateRandomBatch(signer, batchId, timestamp, dataCount, dataSize, tagCount, new LinkedList()); - } - - - /** - * Generates a complete instance of a CompleteBatch - * @param signer contains the credentials required to sign the message - * @param batchId is the (per-signer) batch-ID - * @param dataCount is the number of Batch Data in the batch - * @param dataSize is the number of bytes per Batch Data - * @param tagCount is the number of tags - * @param tags contains a list of tags to be added (in addition to the random ones) - * @return a random, signed CompleteBatch containing random data, tags and timestamp - * @throws SignatureException if an error occurs while signing the batch - */ - public CompleteBatch generateRandomBatch(BatchDigitalSignature signer, int batchId, int dataCount, int dataSize, int tagCount, List tags) throws SignatureException { - - Timestamp timestamp = Timestamp.newBuilder() - .setSeconds(random.nextLong()) - .setNanos(random.nextInt()) - .build(); - - return generateRandomBatch(signer, batchId, timestamp, dataCount, dataSize, tagCount, tags); - - } - - /** - * Generates a complete instance of a CompleteBatch - * @param signer contains the credentials required to sign the message - * @param batchId is the (per-signer) batch-ID - * @param dataCount is the number of Batch Data in the batch - * @param dataSize is the number of bytes per Batch Data - * @param tagCount is the number of tags - * @return a random, signed CompleteBatch containing random data, tags and timestamp - * @throws SignatureException if an error occurs while signing the batch - */ - public CompleteBatch generateRandomBatch(BatchDigitalSignature signer, int batchId, int dataCount, int dataSize, int tagCount) throws SignatureException { - return generateRandomBatch(signer, batchId, dataCount, dataSize, tagCount, new LinkedList()); - } - } diff --git a/meerkat-common/src/main/java/meerkat/util/BulletinBoardUtils.java b/meerkat-common/src/main/java/meerkat/util/BulletinBoardUtils.java index 8793b2d..0189674 100644 --- a/meerkat-common/src/main/java/meerkat/util/BulletinBoardUtils.java +++ b/meerkat-common/src/main/java/meerkat/util/BulletinBoardUtils.java @@ -1,7 +1,9 @@ package meerkat.util; +import com.google.protobuf.ByteString; import meerkat.protobuf.BulletinBoardAPI.*; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; @@ -104,4 +106,150 @@ public class BulletinBoardUtils { return new java.sql.Timestamp(protoTimestamp.getSeconds() * 1000 + protoTimestamp.getNanos() / 1000000); } + /** + * Breaks up a bulletin board message into chunks + * @param msg is the complete message + * @return a list of BatchChunks that contains the raw message data + */ + public static List breakToBatch(BulletinBoardMessage msg, int chunkSize) { + + byte[] data = msg.getMsg().getData().toByteArray(); + + int chunkNum = data.length / chunkSize; + if (data.length % chunkSize != 0) + chunkNum++; + + List chunkList = new ArrayList<>(chunkNum); + + int location = 0; + + for (int i=0 ; i < chunkNum ; i++) { + + int chunkLength; + + if (i == chunkNum - 1){ + chunkLength = data.length % chunkSize; + if (chunkLength == 0){ + chunkLength = chunkSize; + } + } else{ + chunkLength = chunkSize; + } + + chunkList.add(BatchChunk.newBuilder() + .setData(ByteString.copyFrom(data, location, chunkLength)) + .build()); + + location += chunkLength; + + } + + return chunkList; + + } + + /** + * Removes concrete data from the message and turns it into a stub + * @param msg is the original message + * @return the message stub + */ + public static BulletinBoardMessage makeStub(BulletinBoardMessage msg) { + + return BulletinBoardMessage.newBuilder() + .mergeFrom(msg) + .setMsg(UnsignedBulletinBoardMessage.newBuilder(). + mergeFrom(msg.getMsg()) + .setIsStub(true) + .clearData() + .build()) + .build(); + + } + + /** + * Merges a batch chunk list back into a message stub to create a complete Bulletin Board message + * @param msgStub is a message stub + * @param chunkList contains the (ordered) data of the batch message + * @return a complete message containing both data and metadata + */ + public static BulletinBoardMessage gatherBatch(BulletinBoardMessage msgStub, List chunkList) { + + List dataList = new LinkedList<>(); + + for (BatchChunk chunk : chunkList){ + dataList.add(chunk.getData()); + } + + return BulletinBoardMessage.newBuilder() + .mergeFrom(msgStub) + .setMsg(UnsignedBulletinBoardMessage.newBuilder(). + mergeFrom(msgStub.getMsg()) + .setIsStub(false) + .setData(ByteString.copyFrom(dataList)) + .build()) + .build(); + + } + + /** + * Gerenates a BeginBatchMessage Protobuf which is used to begin uploading a message as a batch + * @param signerId is the ID of the message generator + * @param batchId is the identifier for the message + * @param msg is the Bulletin Board message to be uploaded, which can be a stub or a complete message + * @return the required BeginBatchMessage + */ + public static BeginBatchMessage generateBeginBatchMessage(ByteString signerId, int batchId, BulletinBoardMessage msg) { + + if (msg.getSigCount() <= 0){ + throw new IllegalArgumentException("No signatures found"); + } + + return BeginBatchMessage.newBuilder() + .setSignerId(signerId) + .setBatchId(batchId) + .addAllTag(msg.getMsg().getTagList()) + .build(); + + } + + /** + * Gerenates a BeginBatchMessage Protobuf which is used to begin uploading a message as a batch + * @param batchId is the identifier for the message + * @param msg is the Bulletin Board message to be uploaded, which can be a stub or a complete message + * The signer ID used will be the one from the first signature in the message + * @return the required BeginBatchMessage + * @throws IllegalArgumentException if the message contains no signatures + */ + public static BeginBatchMessage generateBeginBatchMessage(int batchId, BulletinBoardMessage msg) throws IllegalArgumentException { + + if (msg.getSigCount() <= 0){ + throw new IllegalArgumentException("No signatures found"); + } + + return generateBeginBatchMessage(msg.getSig(0).getSignerId(), batchId, msg); + + } + + /** + * Gerenates a CloseBatchMessage Protobuf which is used to finalize a batch message + * @param batchId is the identifier for the message + * @param batchLength is the number of chunks in the batch + * @param msg is the Bulletin Board message that was uploaded (and can also be a stub of said message) + * @throws IllegalArgumentException if the message contains no signatures + */ + public static CloseBatchMessage generateCloseBatchMessage(int batchId, int batchLength, BulletinBoardMessage msg) { + + if (msg.getSigCount() <= 0){ + throw new IllegalArgumentException("No signatures found"); + } + + return CloseBatchMessage.newBuilder() + .setTimestamp(msg.getMsg().getTimestamp()) + .setBatchLength(batchLength) + .setBatchId(batchId) + .addAllSig(msg.getSigList()) + .build(); + + } + } diff --git a/meerkat-common/src/main/proto/meerkat/BulletinBoardAPI.proto b/meerkat-common/src/main/proto/meerkat/BulletinBoardAPI.proto index 1136e16..7838fee 100644 --- a/meerkat-common/src/main/proto/meerkat/BulletinBoardAPI.proto +++ b/meerkat-common/src/main/proto/meerkat/BulletinBoardAPI.proto @@ -7,14 +7,6 @@ option java_package = "meerkat.protobuf"; import 'meerkat/crypto.proto'; import 'google/protobuf/timestamp.proto'; -message BoolMsg { - bool value = 1; -} - -message IntMsg { - int32 value = 1; -} - message MessageID { // The ID of a message for unique retrieval. // Note that it is assumed that this ID is a function of the message itself. @@ -28,15 +20,19 @@ message UnsignedBulletinBoardMessage { // Timestamp of the message (as defined by client) google.protobuf.Timestamp timestamp = 2; + // Defines whether or not the message is a stub + // If the message is a stub: the data is ignored + bool isStub = 3; + // The actual content of the message - bytes data = 3; + bytes data = 4; } message BulletinBoardMessage { // Serial entry number of message in database int64 entryNum = 1; - + // Unsigned raw data of message UnsignedBulletinBoardMessage msg = 2; @@ -90,7 +86,7 @@ message MessageFilterList { // This message is used to start a batch transfer to the Bulletin Board Server message BeginBatchMessage { - bytes signerId = 1; // Unique signer identifier + bytes signerId = 1; // Unique identifier of the main signer of the message int32 batchId = 2; // Unique identifier for the batch (unique per signer) repeated string tag = 3; // Tags for the batch message } @@ -100,17 +96,17 @@ message CloseBatchMessage { int32 batchId = 1; // Unique identifier for the batch (unique per signer) int32 batchLength = 2; // Number of messages in the batch google.protobuf.Timestamp timestamp = 3; // Timestamp of the batch (as defined by client) - meerkat.Signature sig = 4; // Signature on the (ordered) batch messages + repeated meerkat.Signature sig = 4; // Signature on the (ordered) batch messages ; First signature belongs to main signer } -// Container for single batch message data -message BatchData { +// Container for single chunk of abatch message +message BatchChunk { bytes data = 1; } -// List of BatchData; Only used for testing -message BatchDataList { - repeated BatchData data = 1; +// List of BatchChunk; Only used for testing +message BatchChunkList { + repeated BatchChunk data = 1; } // These messages comprise a batch message @@ -118,14 +114,7 @@ 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 -} - -// This message defines which batch to read and from which location to start reading -message BatchSpecificationMessage { - bytes signerId = 1; // Unique signer identifier - int32 batchId = 2; // Unique identifier for the batch (unique per signer) - int32 startPosition = 3; // Position in batch to start reading from + BatchChunk data = 4; // Actual data } // This message is used to define a single query to the server to ascertain whether or not the server is synched with the client @@ -168,4 +157,15 @@ message SyncQueryResponse { // Largest value of timestamp for which the checksums match google.protobuf.Timestamp lastTimeOfSync = 2; +} + +// This message defines a query for retrieval of batch data +message BatchQuery { + + // The unique message ID if the batch + MessageID msgID = 1; + + // The first chunk to retrieve (0 is the first chunk) + int32 startPosition = 2; + } \ No newline at end of file diff --git a/meerkat-common/src/main/proto/meerkat/PollingStation.proto b/meerkat-common/src/main/proto/meerkat/PollingStation.proto new file mode 100644 index 0000000..0cdc658 --- /dev/null +++ b/meerkat-common/src/main/proto/meerkat/PollingStation.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package meerkat; + +option java_package = "meerkat.protobuf"; + +// Container for scanned data +message ScannedData { + bytes data = 1; +} + +// Container for error messages +message ErrorMsg { + string msg = 1; +} \ No newline at end of file diff --git a/meerkat-common/src/main/proto/meerkat/comm.proto b/meerkat-common/src/main/proto/meerkat/comm.proto new file mode 100644 index 0000000..6808288 --- /dev/null +++ b/meerkat-common/src/main/proto/meerkat/comm.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package meerkat; + +option java_package = "meerkat.protobuf"; + +message BroadcastMessage { + int32 sender = 1; + int32 destination = 2; + bool is_private = 3; + + bytes payload = 5; +} \ No newline at end of file diff --git a/meerkat-common/src/test/java/meerkat/bulletinboard/BulletinBoardDigestTest.java b/meerkat-common/src/test/java/meerkat/bulletinboard/BulletinBoardDigestTest.java new file mode 100644 index 0000000..277bda1 --- /dev/null +++ b/meerkat-common/src/test/java/meerkat/bulletinboard/BulletinBoardDigestTest.java @@ -0,0 +1,112 @@ +package meerkat.bulletinboard; + +import com.google.protobuf.ByteString; +import meerkat.crypto.concrete.ECDSASignature; +import meerkat.crypto.concrete.SHA256Digest; +import meerkat.protobuf.BulletinBoardAPI.*; +import meerkat.util.BulletinBoardMessageGenerator; +import meerkat.util.BulletinBoardUtils; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.security.*; +import java.security.cert.CertificateException; +import java.util.List; +import java.util.Random; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.fail; + +/** + * Created by Arbel Deutsch Peled on 16-Jun-16. + */ +public class BulletinBoardDigestTest { + + private static String KEYFILE_EXAMPLE = "/certs/enduser-certs/user1-key-with-password-secret.p12"; + private static String KEYFILE_EXAMPLE3 = "/certs/enduser-certs/user3-key-with-password-shh.p12"; + + private static String KEYFILE_PASSWORD1 = "secret"; + private static String KEYFILE_PASSWORD3 = "shh"; + + private GenericBulletinBoardSignature[] signers; + private ByteString[] signerIDs; + + @Before + public void init() { + + signers = new GenericBulletinBoardSignature[2]; + signerIDs = new ByteString[signers.length]; + signers[0] = new GenericBulletinBoardSignature(new ECDSASignature()); + signers[1] = new GenericBulletinBoardSignature(new ECDSASignature()); + + InputStream keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE); + char[] password = KEYFILE_PASSWORD1.toCharArray(); + + KeyStore.Builder keyStoreBuilder = null; + try { + keyStoreBuilder = signers[0].getPKCS12KeyStoreBuilder(keyStream, password); + + signers[0].loadSigningCertificate(keyStoreBuilder); + + keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE3); + password = KEYFILE_PASSWORD3.toCharArray(); + + keyStoreBuilder = signers[1].getPKCS12KeyStoreBuilder(keyStream, password); + signers[1].loadSigningCertificate(keyStoreBuilder); + + for (int i = 0 ; i < signers.length ; i++) { + signerIDs[i] = signers[i].getSignerID(); + } + + } catch (IOException e) { + System.err.println("Failed reading from signature file " + e.getMessage()); + fail("Failed reading from signature file " + e.getMessage()); + } catch (CertificateException e) { + System.err.println("Failed reading certificate " + e.getMessage()); + fail("Failed reading certificate " + e.getMessage()); + } catch (KeyStoreException e) { + System.err.println("Failed reading keystore " + e.getMessage()); + fail("Failed reading keystore " + e.getMessage()); + } catch (NoSuchAlgorithmException e) { + System.err.println("Couldn't find signing algorithm " + e.getMessage()); + fail("Couldn't find signing algorithm " + e.getMessage()); + } catch (UnrecoverableKeyException e) { + System.err.println("Couldn't find signing key " + e.getMessage()); + fail("Couldn't find signing key " + e.getMessage()); + } + + } + + @Test + public void testBatchDigest() throws SignatureException { + + final int MESSAGE_SIZE = 100; + final int CHUNK_SIZE = 10; + final int TAG_NUM = 10; + + BulletinBoardMessageGenerator generator = new BulletinBoardMessageGenerator(new Random(0)); + + BulletinBoardMessage completeMessage = generator.generateRandomMessage(signers, MESSAGE_SIZE, TAG_NUM); + + BulletinBoardMessage stub = BulletinBoardUtils.makeStub(completeMessage); + List batchChunks = BulletinBoardUtils.breakToBatch(completeMessage, CHUNK_SIZE); + + BulletinBoardDigest digest = new GenericBulletinBoardDigest(new SHA256Digest()); + + digest.update(completeMessage); + MessageID id1 = digest.digestAsMessageID(); + + digest.update(stub); + for (BatchChunk batchChunk : batchChunks){ + digest.update(batchChunk.getData().toByteArray()); + } + + MessageID id2 = digest.digestAsMessageID(); + + assertThat("Digests not equal!", id1.getID().equals(id2.getID())); + + } + +} diff --git a/meerkat-common/src/test/java/meerkat/comm/ChannelImpl.java b/meerkat-common/src/test/java/meerkat/comm/ChannelImpl.java new file mode 100644 index 0000000..cfe32c2 --- /dev/null +++ b/meerkat-common/src/test/java/meerkat/comm/ChannelImpl.java @@ -0,0 +1,96 @@ +package meerkat.comm; + +import com.google.protobuf.Message; +import com.google.protobuf.TextFormat; +import meerkat.protobuf.Comm; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.Queue; +import java.util.TreeMap; +import java.util.concurrent.*; + +/** + * Created by Tzlil on 2/14/2016. + */ +// TODO: Change nane to network + +public class ChannelImpl { + final Logger logger = LoggerFactory.getLogger(getClass()); + + //private ExecutorService executorService = Executors.newCachedThreadPool(); + public static int BROADCAST = 0; + Map channels = new TreeMap<>(); + + + public ChannelImpl() { + } + + public Channel getChannel(int id) { + return new SingleChannel(id); + } + + public class SingleChannel implements Channel { + protected final int id; + + ReceiverCallback callback; + + SingleChannel(int id) { + this.id = id; + channels.put(id, this); + } + + @Override + public int getSourceId() { + return id; + } + + + @Override + public void sendMessage(int destUser, Message msg) { + if (destUser < 1) + return; + SingleChannel channel = channels.get(destUser); + if (channel == null) { + logger.warn("Party {} attempting to send message to non-existing party {}", getSourceId(), destUser); + return; + } + Comm.BroadcastMessage broadcastMessage = Comm.BroadcastMessage.newBuilder() + .setSender(id) + .setDestination(destUser) + .setIsPrivate(true) + .setPayload(msg.toByteString()) + .build(); + + logger.debug("sending Message: Dst={},Src={} [{}]", broadcastMessage.getDestination(), + broadcastMessage.getSender(), TextFormat.printToString(msg)); + + channel.callback.receiveMessage(broadcastMessage); + } + + @Override + public void broadcastMessage(Message msg) { + Comm.BroadcastMessage broadcastMessage = Comm.BroadcastMessage.newBuilder() + .setSender(id) + .setDestination(BROADCAST) + .setIsPrivate(false) + .setPayload(msg.toByteString()) + .build(); + + logger.debug("broadcasting Message: Src={} [{}]", + broadcastMessage.getSender(), TextFormat.printToString(msg)); + + for (SingleChannel channel : channels.values()) { + channel.callback.receiveMessage(broadcastMessage); + } + } + + @Override + public void registerReceiverCallback(final ReceiverCallback callback) { + this.callback = callback; + } + } + +} diff --git a/meerkat-common/src/test/java/meerkat/crypto/concrete/ECDSADeterministicSignatureTest.java b/meerkat-common/src/test/java/meerkat/crypto/concrete/ECDSADeterministicSignatureTest.java index cc41497..4baa741 100644 --- a/meerkat-common/src/test/java/meerkat/crypto/concrete/ECDSADeterministicSignatureTest.java +++ b/meerkat-common/src/test/java/meerkat/crypto/concrete/ECDSADeterministicSignatureTest.java @@ -3,7 +3,6 @@ package meerkat.crypto.concrete; import com.google.protobuf.ByteString; import com.google.protobuf.Message; -import meerkat.crypto.concrete.ECDSASignature; import meerkat.protobuf.Crypto; import org.junit.Test; diff --git a/meerkat-common/src/test/java/meerkat/crypto/concrete/ECDSASignatureTest.java b/meerkat-common/src/test/java/meerkat/crypto/concrete/ECDSASignatureTest.java index 37c2d1b..e5b448d 100644 --- a/meerkat-common/src/test/java/meerkat/crypto/concrete/ECDSASignatureTest.java +++ b/meerkat-common/src/test/java/meerkat/crypto/concrete/ECDSASignatureTest.java @@ -3,7 +3,6 @@ package meerkat.crypto.concrete; import com.google.protobuf.ByteString; import com.google.protobuf.Message; import meerkat.protobuf.Crypto; -import meerkat.crypto.concrete.ECDSASignature; import org.junit.Before; import org.junit.Test; diff --git a/polling-station/build.gradle b/polling-station/build.gradle index 34fe58d..68aad49 100644 --- a/polling-station/build.gradle +++ b/polling-station/build.gradle @@ -41,6 +41,14 @@ version += "${isSnapshot ? '-SNAPSHOT' : ''}" dependencies { // Meerkat common compile project(':meerkat-common') + compile project(':restful-api-common') + + // Jersey for RESTful API + compile 'org.glassfish.jersey.containers:jersey-container-servlet:2.5.+' + + // Servlets + compile 'org.eclipse.jetty:jetty-server:9.3.+' + compile 'org.eclipse.jetty:jetty-servlet:9.3.+' // Logging compile 'org.slf4j:slf4j-api:1.7.7' diff --git a/polling-station/src/main/java/meerkat/pollingstation/PollingStationScannerWebApp.java b/polling-station/src/main/java/meerkat/pollingstation/PollingStationScannerWebApp.java new file mode 100644 index 0000000..327c774 --- /dev/null +++ b/polling-station/src/main/java/meerkat/pollingstation/PollingStationScannerWebApp.java @@ -0,0 +1,88 @@ +package meerkat.pollingstation; + +/** + * Created by Arbel on 5/31/2016. + */ + +import com.google.common.util.concurrent.FutureCallback; +import com.google.protobuf.BoolValue; +import meerkat.protobuf.PollingStation; + +import javax.annotation.PostConstruct; +import javax.servlet.ServletContext; +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import java.io.IOException; + +import static meerkat.pollingstation.PollingStationConstants.POLLING_STATION_WEB_SCANNER_ERROR_PATH; +import static meerkat.pollingstation.PollingStationConstants.POLLING_STATION_WEB_SCANNER_SCAN_PATH; +import static meerkat.rest.Constants.MEDIATYPE_PROTOBUF; + +/** + * Implements a Web-App interface for {@link meerkat.pollingstation.PollingStationScanner.Producer} + * This class depends on {@link meerkat.pollingstation.PollingStationWebScanner} and works in conjunction with it + */ +@Path("/") +public class PollingStationScannerWebApp implements PollingStationScanner.Producer { + + @Context + ServletContext servletContext; + + Iterable> callbacks; + + /** + * This method is called by the Jetty engine when instantiating the servlet + */ + @PostConstruct + public void init() throws Exception{ + callbacks = (Iterable>) servletContext.getAttribute(PollingStationWebScanner.CALLBACKS_ATTRIBUTE_NAME); + } + + @POST + @Path(POLLING_STATION_WEB_SCANNER_SCAN_PATH) + @Consumes(MEDIATYPE_PROTOBUF) + @Produces(MEDIATYPE_PROTOBUF) + @Override + public BoolValue newScan(PollingStation.ScannedData scannedData) { + + boolean handled = false; + + for (FutureCallback callback : callbacks){ + + callback.onSuccess(scannedData); + handled = true; + + } + + return BoolValue.newBuilder() + .setValue(handled) + .build(); + + } + + @POST + @Path(POLLING_STATION_WEB_SCANNER_ERROR_PATH) + @Consumes(MEDIATYPE_PROTOBUF) + @Produces(MEDIATYPE_PROTOBUF) + @Override + public BoolValue reportScanError(PollingStation.ErrorMsg errorMsg) { + + boolean handled = false; + + for (FutureCallback callback : callbacks){ + + callback.onFailure(new IOException(errorMsg.getMsg())); + handled = true; + + } + + return BoolValue.newBuilder() + .setValue(handled) + .build(); + + } + +} diff --git a/polling-station/src/main/java/meerkat/pollingstation/PollingStationWebScanner.java b/polling-station/src/main/java/meerkat/pollingstation/PollingStationWebScanner.java new file mode 100644 index 0000000..a4e290a --- /dev/null +++ b/polling-station/src/main/java/meerkat/pollingstation/PollingStationWebScanner.java @@ -0,0 +1,60 @@ +package meerkat.pollingstation; + +import java.util.List; +import java.util.LinkedList; + +import com.google.common.util.concurrent.FutureCallback; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.*; + +import org.glassfish.jersey.servlet.ServletContainer; +import org.glassfish.jersey.server.ResourceConfig; + +import meerkat.protobuf.PollingStation.ScannedData; +import meerkat.rest.*; + +/** + * Created by Arbel on 05/05/2016. + */ + +public class PollingStationWebScanner implements PollingStationScanner.Consumer{ + + public final static String CALLBACKS_ATTRIBUTE_NAME = "callbacks"; + + private final Server server; + private final List> callbacks; + + public PollingStationWebScanner(int port, String subAddress) { + + callbacks = new LinkedList<>(); + + server = new Server(port); + + ServletContextHandler servletContextHandler = new ServletContextHandler(server, subAddress); + servletContextHandler.setAttribute(CALLBACKS_ATTRIBUTE_NAME, (Iterable>) callbacks); + + ResourceConfig resourceConfig = new ResourceConfig(PollingStationScannerWebApp.class); + resourceConfig.register(ProtobufMessageBodyReader.class); + resourceConfig.register(ProtobufMessageBodyWriter.class); + + ServletHolder servletHolder = new ServletHolder(new ServletContainer(resourceConfig)); + + servletContextHandler.addServlet(servletHolder, "/*"); + } + + @Override + public void start() throws Exception { + server.start(); + } + + @Override + public void stop() throws Exception { + server.stop(); + } + + @Override + public void subscribe(FutureCallback scanCallback) { + callbacks.add(scanCallback); + } + +} diff --git a/polling-station/src/test/java/meerkat/pollingstation/PollingStationWebScannerTest.java b/polling-station/src/test/java/meerkat/pollingstation/PollingStationWebScannerTest.java new file mode 100644 index 0000000..7da106c --- /dev/null +++ b/polling-station/src/test/java/meerkat/pollingstation/PollingStationWebScannerTest.java @@ -0,0 +1,162 @@ +package meerkat.pollingstation; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.protobuf.ByteString; +import meerkat.protobuf.PollingStation.*; +import meerkat.rest.Constants; + +import meerkat.rest.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.Response; + +import java.util.concurrent.Semaphore; + +import static meerkat.pollingstation.PollingStationConstants.*; + +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Created by Arbel on 25/05/2016. + */ +public class PollingStationWebScannerTest { + + private PollingStationScanner.Consumer scanner; + private static final String ADDRESS = "http://localhost"; + private static final String SUB_ADDRESS = ""; + private static final int PORT = 8080; + + private Semaphore semaphore; + private Throwable thrown; + private boolean dataIsAsExpected; + + private class ScanHandler implements FutureCallback { + + private final ScannedData expectedData; + + public ScanHandler(ScannedData expectedData) { + this.expectedData = expectedData; + } + + @Override + public void onSuccess(ScannedData result) { + dataIsAsExpected = result.getData().equals(expectedData.getData()); + semaphore.release(); + } + + @Override + public void onFailure(Throwable t) { + dataIsAsExpected = false; + thrown = t; + semaphore.release(); + } + } + + private class ErrorHandler implements FutureCallback { + + private final String expectedErrorMessage; + + public ErrorHandler(String expectedErrorMessage) { + this.expectedErrorMessage = expectedErrorMessage; + } + + @Override + public void onSuccess(ScannedData result) { + dataIsAsExpected = false; + semaphore.release(); + } + + @Override + public void onFailure(Throwable t) { + dataIsAsExpected = t.getMessage().equals(expectedErrorMessage); + semaphore.release(); + } + } + + @Before + public void init() { + + System.err.println("Setting up Scanner WebApp!"); + + scanner = new PollingStationWebScanner(PORT, SUB_ADDRESS); + + semaphore = new Semaphore(0); + thrown = null; + + try { + scanner.start(); + } catch (Exception e) { + assertThat("Could not start server: " + e.getMessage(), false); + } + + } + + @Test + public void testSuccessfulScan() throws InterruptedException { + + Client client = ClientBuilder.newClient(); + client.register(ProtobufMessageBodyReader.class); + client.register(ProtobufMessageBodyWriter.class); + WebTarget webTarget = client.target(ADDRESS + ":" + PORT) + .path(SUB_ADDRESS).path(POLLING_STATION_WEB_SCANNER_SCAN_PATH); + + byte[] data = {(byte) 1, (byte) 2}; + + ScannedData scannedData = ScannedData.newBuilder() + .setData(ByteString.copyFrom(data)) + .build(); + + scanner.subscribe(new ScanHandler(scannedData)); + + Response response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(scannedData, Constants.MEDIATYPE_PROTOBUF)); + response.close(); + + semaphore.acquire(); + assertThat("Scanner has thrown an error", thrown == null); + assertThat("Scanned data received was incorrect", dataIsAsExpected); + + } + + @Test + public void testErroneousScan() throws InterruptedException { + + Client client = ClientBuilder.newClient(); + client.register(ProtobufMessageBodyReader.class); + client.register(ProtobufMessageBodyWriter.class); + WebTarget webTarget = client.target(ADDRESS + ":" + PORT) + .path(SUB_ADDRESS).path(POLLING_STATION_WEB_SCANNER_ERROR_PATH); + + ErrorMsg errorMsg = ErrorMsg.newBuilder() + .setMsg("!Error Message!") + .build(); + + scanner.subscribe(new ErrorHandler(errorMsg.getMsg())); + + Response response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(errorMsg, Constants.MEDIATYPE_PROTOBUF)); + response.close(); + + semaphore.acquire(); + assertThat("Scanner error received was incorrect", dataIsAsExpected); + + } + + @After + public void close() { + + System.err.println("Scanner WebApp shutting down..."); + + try { + scanner.stop(); + } catch (Exception e) { + assertThat("Could not stop server: " + e.getMessage(), false); + } + + } + +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 99f4c5e..851bfc3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,4 +4,5 @@ include 'bulletin-board-server' include 'polling-station' include 'restful-api-common' include 'bulletin-board-client' +include 'distributed-key-generation'