Compare commits
7 Commits
Author | SHA1 | Date |
---|---|---|
Hai Brenner | ccf6171f9b | |
Hai Brenner | 3f5b5ab1e4 | |
Hai Brenner | 9d19d82477 | |
Hai Brenner | e2708af39e | |
Hai Brenner | a7585e4b5f | |
Hai Brenner | f1c5a7d204 | |
Hai Brenner | 6e64c57431 |
|
@ -22,21 +22,21 @@ public interface VotingBooth {
|
|||
*
|
||||
* Called by votingbooth thread.
|
||||
*/
|
||||
void commitToEncryptedBallot(EncryptedBallot ballot);
|
||||
void commitToEncryptedBallot(EncryptedBallot ballot, BallotSecrets secrets);
|
||||
|
||||
|
||||
/**
|
||||
* Finalize a vote for casting
|
||||
* Called by votingbooth in case user decides to cast.
|
||||
*/
|
||||
void castVote();
|
||||
//void castVote();
|
||||
|
||||
/**
|
||||
* Submit audit information and spoil vote.
|
||||
* Called by votingbooth in case user decides to audit
|
||||
* @param ballotSecrets
|
||||
*/
|
||||
void auditVote(BallotSecrets ballotSecrets);
|
||||
//void auditVote(BallotSecrets ballotSecrets);
|
||||
|
||||
|
||||
}
|
||||
|
@ -60,11 +60,11 @@ public interface VotingBooth {
|
|||
/**
|
||||
* UI calls this when the user cancels the voting process in the middle.
|
||||
*/
|
||||
void cancelBallot();
|
||||
//void cancelBallot();
|
||||
|
||||
/**
|
||||
* Called by UI thread after voter made choice to cast or audit ballot.
|
||||
* @param castVote
|
||||
*/
|
||||
void voterCastOrAudit(boolean castVote);
|
||||
//void voterCastOrAudit(boolean castVote);
|
||||
}
|
||||
|
|
|
@ -52,6 +52,36 @@ message BallotAnswerTranslationTable {
|
|||
bytes data = 1;
|
||||
}
|
||||
|
||||
|
||||
// Type of the element data to be presented by UI
|
||||
enum UIElementDataType {
|
||||
TEXT = 0;
|
||||
IMAGE = 1;
|
||||
VOICE = 2;
|
||||
}
|
||||
|
||||
// Type of question
|
||||
enum QuestionType {
|
||||
MULTIPLE_CHOICE = 0;
|
||||
MULTIPLE_SELECTION = 1;
|
||||
ORDER = 2;
|
||||
}
|
||||
|
||||
// An element to be presented by UI
|
||||
message UIElement {
|
||||
UIElementDataType type = 1;
|
||||
bytes data = 2;
|
||||
}
|
||||
|
||||
// a new data structure for BallotQuestion. Need to delete the old one
|
||||
message BallotQuestionNew {
|
||||
bool is_mandatory = 1;
|
||||
UIElement question = 2;
|
||||
UIElement description = 3;
|
||||
repeated UIElement answer = 4;
|
||||
}
|
||||
|
||||
|
||||
// Data required in order to access the Bulletin Board Servers
|
||||
message BulletinBoardClientParams {
|
||||
|
||||
|
@ -80,10 +110,10 @@ message ElectionParams {
|
|||
uint32 mixerThreshold = 5;
|
||||
|
||||
// Candidate list (or other question format)
|
||||
repeated BallotQuestion questions = 6;
|
||||
repeated BallotQuestionNew question = 6;
|
||||
|
||||
// Translation table between answers and plaintext encoding
|
||||
BallotAnswerTranslationTable answerTranslationTable = 7;
|
||||
//BallotAnswerTranslationTable answerTranslationTable = 7;
|
||||
|
||||
// Data required in order to access the Bulletin Board Servers
|
||||
BulletinBoardClientParams bulletinBoardClientParams = 8;
|
||||
|
|
|
@ -54,6 +54,15 @@ dependencies {
|
|||
runtime 'org.codehaus.groovy:groovy:2.4.+'
|
||||
}
|
||||
|
||||
test {
|
||||
exclude '**/*IntegrationTest*'
|
||||
exclude '**/*ManualTest*'
|
||||
}
|
||||
|
||||
task manualTest(type: Test) {
|
||||
include '**/*ManualTest*'
|
||||
}
|
||||
|
||||
|
||||
/*==== You probably don't have to edit below this line =======*/
|
||||
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
package meerkat.voting;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Random;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import com.google.protobuf.Message;
|
||||
|
||||
import meerkat.crypto.Encryption;
|
||||
import meerkat.protobuf.Crypto.EncryptionPublicKey;
|
||||
import meerkat.protobuf.Crypto.EncryptionRandomness;
|
||||
import meerkat.protobuf.Crypto.RerandomizableEncryptedMessage;
|
||||
|
||||
public class EncryptionFake implements Encryption {
|
||||
|
||||
EncryptionPublicKey m_encryptionPublicKey;
|
||||
Random m_rnd;
|
||||
|
||||
public EncryptionFake (EncryptionPublicKey publicKey) {
|
||||
m_encryptionPublicKey = publicKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RerandomizableEncryptedMessage encrypt(Message plaintext, EncryptionRandomness rnd) throws IOException {
|
||||
// TODO Auto-generated method stub
|
||||
assert plaintext != null;
|
||||
assert rnd != null;
|
||||
|
||||
byte plaintextBytes[] = plaintext.toByteArray();
|
||||
byte encryptedBytes[] = new byte[plaintextBytes.length + 1];
|
||||
encryptedBytes[0] = rnd.getData().byteAt(0);
|
||||
for (int i = 0; i < plaintextBytes.length; ++i) {
|
||||
encryptedBytes[i+1] = (byte)(plaintextBytes[i] ^ encryptedBytes[0]);
|
||||
}
|
||||
|
||||
return RerandomizableEncryptedMessage.newBuilder().setData(ByteString.copyFrom(encryptedBytes)).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RerandomizableEncryptedMessage rerandomize(RerandomizableEncryptedMessage msg, EncryptionRandomness rnd)
|
||||
throws InvalidProtocolBufferException {
|
||||
// TODO Auto-generated method stub
|
||||
System.err.println("rerandomize: not implemented!");
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EncryptionRandomness generateRandomness(Random rand) {
|
||||
assert rand != null;
|
||||
byte a[] = new byte[1];
|
||||
a[0] = (byte)((rand.nextInt() % 255)+1);
|
||||
ByteString val = ByteString.copyFrom(a);
|
||||
return EncryptionRandomness.newBuilder().setData(val).build();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package meerkat.voting;
|
||||
|
||||
import meerkat.protobuf.Voting.EncryptedBallot;
|
||||
|
||||
public class SharedEncryptedBallotMessage {
|
||||
|
||||
private EncryptedBallot m_encryptedBallot;
|
||||
private boolean b_writeable = true;
|
||||
|
||||
synchronized void setSharedMessage (EncryptedBallot encryptedBallot)
|
||||
{
|
||||
while (!b_writeable) {
|
||||
try
|
||||
{
|
||||
wait ();
|
||||
}
|
||||
catch (InterruptedException e) {}
|
||||
}
|
||||
|
||||
m_encryptedBallot = EncryptedBallot.newBuilder(encryptedBallot).build();
|
||||
b_writeable = false;
|
||||
notifyAll ();
|
||||
}
|
||||
|
||||
synchronized EncryptedBallot getSharedMessage (int millisecTimeout)
|
||||
{
|
||||
do {
|
||||
try {
|
||||
wait (millisecTimeout);
|
||||
}
|
||||
catch (InterruptedException e) { }
|
||||
} while (b_writeable && millisecTimeout == 0);
|
||||
|
||||
EncryptedBallot retValue = null;
|
||||
|
||||
if (m_encryptedBallot != null)
|
||||
{
|
||||
b_writeable = true;
|
||||
notifyAll ();
|
||||
retValue = EncryptedBallot.newBuilder(m_encryptedBallot).build();
|
||||
m_encryptedBallot = null;
|
||||
}
|
||||
|
||||
return retValue;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package meerkat.voting;
|
||||
|
||||
import meerkat.protobuf.Voting.PlaintextBallot;
|
||||
|
||||
public class SharedPlaintextBallotMessage {
|
||||
|
||||
private PlaintextBallot m_plaintextBallot;
|
||||
private boolean b_writeable = true;
|
||||
|
||||
synchronized void setSharedMessage (PlaintextBallot plaintextBallot)
|
||||
{
|
||||
while (!b_writeable) {
|
||||
try
|
||||
{
|
||||
wait ();
|
||||
}
|
||||
catch (InterruptedException e) {}
|
||||
}
|
||||
|
||||
m_plaintextBallot = PlaintextBallot.newBuilder(plaintextBallot).build();
|
||||
b_writeable = false;
|
||||
notifyAll ();
|
||||
}
|
||||
|
||||
synchronized PlaintextBallot getSharedMessage ()
|
||||
{
|
||||
while (b_writeable) {
|
||||
try
|
||||
{
|
||||
wait ();
|
||||
}
|
||||
catch (InterruptedException e) { }
|
||||
}
|
||||
|
||||
b_writeable = true;
|
||||
notifyAll ();
|
||||
|
||||
return PlaintextBallot.newBuilder(m_plaintextBallot).build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package meerkat.voting;
|
||||
|
||||
import meerkat.protobuf.Voting.BallotSecrets;
|
||||
import meerkat.protobuf.Voting.EncryptedBallot;
|
||||
import meerkat.protobuf.Voting.PlaintextBallot;
|
||||
|
||||
public class VBMessage {
|
||||
|
||||
public enum VBMessageType
|
||||
{
|
||||
VB_TICK,
|
||||
VB_CONTROLLER_RESPONSE,
|
||||
VB_ENCRYPTION_QUERY
|
||||
}
|
||||
|
||||
private VBMessageType m_type;
|
||||
private EncryptedBallot m_encryptedBallot;
|
||||
private PlaintextBallot m_plaintextBallot;
|
||||
private BallotSecrets m_secrets;
|
||||
|
||||
public static VBMessage newTick () {
|
||||
VBMessage retVal = new VBMessage();
|
||||
retVal.m_type = VBMessageType.VB_TICK;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public VBMessageType getType () {
|
||||
return m_type;
|
||||
}
|
||||
|
||||
public static VBMessage newEncryptionQuery (PlaintextBallot plaintextBallot) {
|
||||
VBMessage retVal = new VBMessage();
|
||||
retVal.m_type = VBMessageType.VB_ENCRYPTION_QUERY;
|
||||
retVal.m_plaintextBallot = plaintextBallot;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public static VBMessage newControllerResponse (EncryptedBallot encryptedBallot, BallotSecrets ballotSecrets) {
|
||||
VBMessage retVal = new VBMessage();
|
||||
retVal.m_type = VBMessageType.VB_CONTROLLER_RESPONSE;
|
||||
retVal.m_encryptedBallot = encryptedBallot;
|
||||
retVal.m_secrets = ballotSecrets;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public EncryptedBallot getEncryptedBallot () {
|
||||
assert getType() == VBMessageType.VB_CONTROLLER_RESPONSE;
|
||||
return m_encryptedBallot;
|
||||
}
|
||||
|
||||
public BallotSecrets getSecrets () {
|
||||
assert getType() == VBMessageType.VB_CONTROLLER_RESPONSE;
|
||||
return m_secrets;
|
||||
}
|
||||
|
||||
public PlaintextBallot getPlaintextBallot () {
|
||||
assert getType() == VBMessageType.VB_ENCRYPTION_QUERY;
|
||||
return m_plaintextBallot;
|
||||
}
|
||||
|
||||
public boolean isEmptyMessage () {
|
||||
return (getType() == VBMessageType.VB_TICK);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,282 @@
|
|||
package meerkat.voting;
|
||||
|
||||
import meerkat.crypto.concrete.ECElGamalEncryption;
|
||||
import meerkat.crypto.concrete.GlobalCryptoSetup;
|
||||
import meerkat.protobuf.ConcreteCrypto;
|
||||
import meerkat.protobuf.Voting.*;
|
||||
import meerkat.crypto.Encryption;
|
||||
import meerkat.protobuf.Crypto.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import org.bouncycastle.jce.spec.*;
|
||||
import org.bouncycastle.math.ec.ECPoint;
|
||||
import org.factcenter.qilin.primitives.concrete.ECElGamal;
|
||||
import org.factcenter.qilin.primitives.concrete.ECGroup;
|
||||
import org.factcenter.qilin.primitives.generic.ElGamal;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
public class VotingBoothToy implements VotingBooth, Runnable {
|
||||
|
||||
//private ElectionParams m_electionParams;
|
||||
private EncryptionPublicKey m_ballotEncryptionKey;
|
||||
//private List<BallotQuestionNew> l_questions;
|
||||
//private BallotQuestionNew a_questions[];
|
||||
//private BallotAnswer a_answers[];
|
||||
|
||||
private ArrayBlockingQueue<VBMessage> a_queue;
|
||||
static private int m_queueSize = 5;
|
||||
|
||||
private VotingBooth.UI m_ui;
|
||||
|
||||
//TODO: where do I use these?
|
||||
//SignatureVerificationKey a_signatureKeys[];
|
||||
|
||||
Encryption m_encryptor;
|
||||
Random m_random;
|
||||
|
||||
Logger logger;
|
||||
|
||||
|
||||
public VotingBoothToy () {
|
||||
logger = LoggerFactory.getLogger(VotingBoothToy.class);
|
||||
a_queue = new ArrayBlockingQueue<VBMessage> (m_queueSize, true);
|
||||
|
||||
}
|
||||
|
||||
public void registerUI (UI ui) {
|
||||
m_ui = ui;
|
||||
}
|
||||
|
||||
public void run () {
|
||||
System.err.println("VB controller is up and running");
|
||||
|
||||
while (true) {
|
||||
VBMessage msg;
|
||||
try {
|
||||
msg = a_queue.take();
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
System.err.println("VB controller interrupted!!");
|
||||
break;
|
||||
}
|
||||
if (msg.getType() == VBMessage.VBMessageType.VB_ENCRYPTION_QUERY) {
|
||||
handleEncryptionQuery (msg);
|
||||
}
|
||||
else {
|
||||
System.err.println("VB controller unknown type of message!!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void handleEncryptionQuery (VBMessage msg) {
|
||||
assert msg.getType() == VBMessage.VBMessageType.VB_ENCRYPTION_QUERY;
|
||||
PlaintextBallot ptb = msg.getPlaintextBallot();
|
||||
|
||||
// encrypt
|
||||
RerandomizableEncryptedMessage encMsg;
|
||||
EncryptionRandomness rnd = m_encryptor.generateRandomness(m_random);
|
||||
try {
|
||||
// simulate taking a lot of time
|
||||
Thread.sleep(5000);
|
||||
encMsg = m_encryptor.encrypt(ptb, rnd);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
System.err.println("debug: VotingBoothToy encrypt interrupted!");
|
||||
return;
|
||||
}
|
||||
catch (IOException e) {
|
||||
System.err.println("debug: VotingBoothToy encrypt IOException!");
|
||||
return;
|
||||
}
|
||||
|
||||
EncryptedBallot encBallot = EncryptedBallot.newBuilder()
|
||||
.setSerialNumber(ptb.getSerialNumber())
|
||||
.setData(encMsg)
|
||||
.build();
|
||||
RandomnessGenerationProof proof = RandomnessGenerationProof.newBuilder()
|
||||
.setData(ByteString.copyFrom(new byte[0]))
|
||||
.build();
|
||||
BallotSecrets secrets = BallotSecrets.newBuilder()
|
||||
.setPlaintextBallot(ptb)
|
||||
.setEncryptionRandomness(rnd)
|
||||
.setProof(proof)
|
||||
.build();
|
||||
|
||||
m_ui.commitToEncryptedBallot(encBallot, secrets);
|
||||
}
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see meerkat.voting.VotingBooth#init(meerkat.protobuf.Voting.ElectionParams, meerkat.protobuf.Voting.BoothParams)
|
||||
*/
|
||||
@Override
|
||||
public void init(ElectionParams globalParams, BoothParams boothParams) {
|
||||
System.err.println ("debug VB: init.");
|
||||
this.m_ballotEncryptionKey = globalParams.getBallotEncryptionKey();
|
||||
//this.l_questions = globalParams.getQuestionsList();
|
||||
|
||||
// List<SignatureVerificationKey> l_signatureKeys = boothParams.getPscVerificationKeysList();
|
||||
// a_signatureKeys = new SignatureVerificationKey[l_signatureKeys.size()];
|
||||
// int i = 0;
|
||||
// for (SignatureVerificationKey q: l_signatureKeys) {
|
||||
// a_signatureKeys[i] = q;
|
||||
// ++i;
|
||||
// }
|
||||
|
||||
m_random = new Random();
|
||||
ECGroup group = new ECGroup("secp256k1");
|
||||
java.math.BigInteger sk = ECElGamal.generateSecretKey(group, m_random);
|
||||
ElGamal.SK<ECPoint> key = new ECElGamal.SK(group, sk);
|
||||
ConcreteCrypto.ElGamalPublicKey serializedPk = serializePk(group, key);
|
||||
m_encryptor = new ECElGamalEncryption();
|
||||
m_encryptor.init(serializedPk);
|
||||
|
||||
|
||||
//TODO: generate my own signature key
|
||||
System.err.println("Here the controller should create its own signature key");
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method was copied from a test file
|
||||
* It must be resolved in another way!
|
||||
* //TODO: where should this method be declared?
|
||||
*/
|
||||
public ConcreteCrypto.ElGamalPublicKey serializePk(ECGroup group, ElGamal.PK<ECPoint> pk) {
|
||||
ECPoint pkPoint = pk.getPK();
|
||||
ECParameterSpec params = group.getCurveParams();
|
||||
|
||||
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(pkPoint, params);
|
||||
|
||||
try {
|
||||
final String ENCRYPTION_KEY_ALGORITHM = "ECDH";
|
||||
|
||||
KeyFactory fact = KeyFactory.getInstance(ENCRYPTION_KEY_ALGORITHM,
|
||||
GlobalCryptoSetup.getBouncyCastleProvider());
|
||||
PublicKey javaPk = fact.generatePublic(pubKeySpec);
|
||||
ConcreteCrypto.ElGamalPublicKey serializedPk = ConcreteCrypto.ElGamalPublicKey.newBuilder()
|
||||
.setSubjectPublicKeyInfo(ByteString.copyFrom(javaPk.getEncoded())).build();
|
||||
|
||||
return serializedPk;
|
||||
} catch (NoSuchAlgorithmException |InvalidKeySpecException e) {
|
||||
System.err.println("Should never happen!");
|
||||
throw new RuntimeException("Error converting public key!", e);
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see meerkat.voting.VotingBooth#submitBallot(meerkat.protobuf.Voting.PlaintextBallot)
|
||||
*/
|
||||
@Override
|
||||
public void submitBallot(PlaintextBallot ballot) {
|
||||
VBMessage msg = VBMessage.newEncryptionQuery(ballot);
|
||||
try {
|
||||
a_queue.put(msg);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
System.err.println("SubmitBallot interrupted!!");
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see meerkat.voting.VotingBooth#cancelBallot()
|
||||
*/
|
||||
/*@Override
|
||||
public void cancelBallot() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}*/
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see meerkat.voting.VotingBooth#voterCastOrAudit(boolean)
|
||||
*/
|
||||
/*@Override
|
||||
public void voterCastOrAudit(boolean castVote) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}*/
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
|
||||
//A ballot question. This is an opaque
|
||||
//data type that is parsed by the UI to display
|
||||
//the question.
|
||||
message BallotQuestion {
|
||||
bytes data = 1;
|
||||
}
|
||||
|
||||
//An answer to a specific ballot question.
|
||||
//The answer is a vector of signed integers,
|
||||
//to encompass voting schemes such as ranked voting
|
||||
//and STV.
|
||||
message BallotAnswer {
|
||||
repeated sint64 answer = 1 [packed=true];
|
||||
}
|
||||
|
||||
message EncryptedBallot {
|
||||
uint64 serialNumber = 1; // Ballot serial number
|
||||
|
||||
RerandomizableEncryptedMessage data = 2;
|
||||
}
|
||||
|
||||
message BallotSecrets {
|
||||
PlaintextBallot plaintext_ballot = 1;
|
||||
|
||||
EncryptionRandomness encryption_randomness = 2;
|
||||
RandomnessGenerationProof proof = 3;
|
||||
}
|
||||
|
||||
message BoothParams {
|
||||
repeated SignatureVerificationKey pscVerificationKeys = 1;
|
||||
|
||||
}
|
||||
|
||||
//A table to translate to and from compactly encoded answers
|
||||
//and their human-understandable counterparts.
|
||||
//This should be parsable by the UI
|
||||
message BallotAnswerTranslationTable {
|
||||
bytes data = 1;
|
||||
}
|
||||
|
||||
message ElectionParams {
|
||||
// TODO: different sets of keys for different roles?
|
||||
//repeated SignatureVerificationKey trusteeVerificationKeys = 1;
|
||||
|
||||
// How many trustees must participate in a signature for it to be considered valid.
|
||||
//uint32 trusteeSignatureThreshold = 2;
|
||||
|
||||
// The key used to encrypt ballots. The corresponding private key
|
||||
// is shared between the trustees.
|
||||
EncryptionPublicKey ballotEncryptionKey = 3;
|
||||
|
||||
// Verification keys for valid mixers.
|
||||
//repeated SignatureVerificationKey mixerVerificationKeys = 4;
|
||||
|
||||
// How many mixers must participate for the mixing to be considered valid
|
||||
//uint32 mixerThreshold = 5;
|
||||
|
||||
// Candidate list (or other question format)
|
||||
repeated BallotQuestion questions = 6;
|
||||
|
||||
// Translation table between answers and plaintext encoding
|
||||
BallotAnswerTranslationTable answerTranslationTable = 7;
|
||||
|
||||
}
|
||||
*/
|
|
@ -0,0 +1,463 @@
|
|||
package meerkat.voting;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import meerkat.protobuf.Voting;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
|
||||
|
||||
public class VotingBoothToyConsoleUI implements VotingBooth.UI, Runnable {
|
||||
private BufferedReader m_in;
|
||||
private VotingBooth m_vbController;
|
||||
private ArrayBlockingQueue<VBMessage> a_queue;
|
||||
static private int m_queueSize = 5;
|
||||
private Voting.BallotQuestionNew a_questions[];
|
||||
private int m_serialNumber;
|
||||
private Voting.PlaintextBallot m_plaintextBallot;
|
||||
private Voting.EncryptedBallot m_encryptedBallot;
|
||||
private Voting.BallotSecrets m_ballotSecrets;
|
||||
private final Logger logger;
|
||||
private String m_channel;
|
||||
private boolean b_isInitialized;
|
||||
private boolean b_allowRecastOfAudittedBallot;
|
||||
private int m_waitForControllerMillisecTimeout = 100;
|
||||
|
||||
/*
|
||||
* constructor for the Console-UI class
|
||||
*/
|
||||
public VotingBoothToyConsoleUI() {
|
||||
logger = LoggerFactory.getLogger(VotingBoothToyConsoleUI.class);
|
||||
|
||||
a_queue = new ArrayBlockingQueue<>(m_queueSize, true);
|
||||
m_in = new BufferedReader(new InputStreamReader(System.in));
|
||||
|
||||
b_isInitialized = false;
|
||||
b_allowRecastOfAudittedBallot = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* a function for setting behavior of the VotingBoothToy.
|
||||
* If it is set to true, then a ballot which was auditted can be printed again for casting.
|
||||
* If it is false, then after auditing, the voter has to answer all questions again.
|
||||
* Default is false (cannot re-cast an auditted ballot)
|
||||
*/
|
||||
public void setAllowRecastOfAudittedBallot (boolean b) {
|
||||
b_allowRecastOfAudittedBallot = b;
|
||||
}
|
||||
|
||||
/*
|
||||
* initialize the election params
|
||||
*/
|
||||
public void init(Voting.ElectionParams globalParams) {
|
||||
m_serialNumber = 0;
|
||||
|
||||
List<Voting.BallotQuestionNew> l_questions = globalParams.getQuestionList();
|
||||
a_questions = new Voting.BallotQuestionNew[l_questions.size()];
|
||||
|
||||
int i = 0;
|
||||
for (Voting.BallotQuestionNew q: l_questions) {
|
||||
a_questions[i] = q;
|
||||
++i;
|
||||
}
|
||||
|
||||
b_isInitialized = true;
|
||||
}
|
||||
|
||||
|
||||
public void run() {
|
||||
logger.debug("run: initializing the magic!");
|
||||
|
||||
if ((! b_isInitialized) || isHardwareKeyInserted())
|
||||
{
|
||||
runAdminSetupScenario();
|
||||
}
|
||||
|
||||
runVoterScenario();
|
||||
}
|
||||
|
||||
/*
|
||||
* checks if someone inserted a hardware key.
|
||||
* This is used to decided whther to run Voter scenario or Admin-Setup scenario
|
||||
*/
|
||||
private boolean isHardwareKeyInserted() {
|
||||
logger.warn("isHardwareKeyInserted: currently always false -> so running Voter scenario");
|
||||
return false;
|
||||
}
|
||||
|
||||
private void runVoterScenario() {
|
||||
while (true) {
|
||||
logger.debug("votingBegin: preparing console UI for a new user.");
|
||||
// clear history from memory
|
||||
eraseEncryption();
|
||||
erasePlaintext();
|
||||
showWelcomeScreen();
|
||||
++m_serialNumber;
|
||||
runVotingFlow();
|
||||
}
|
||||
}
|
||||
|
||||
private void runAdminSetupScenario() {
|
||||
if (! isHardwareKeyInserted())
|
||||
{
|
||||
System.out.println ("Waiting For hardware key to be inserted by admin...");
|
||||
showNotInitializedScreen();
|
||||
}
|
||||
while (! isHardwareKeyInserted())
|
||||
{
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
logger.error ("runAdminSetupScenario: interrupted while waiting for admin hardware key to be inserted");
|
||||
throw new RuntimeException("runAdminSetupScenario: interrupted while waiting for admin hardware key to be inserted");
|
||||
}
|
||||
}
|
||||
System.out.println ("runAdminSetupScenario: found hardware key");
|
||||
throw new UnsupportedOperationException("runAdminSetupScenario: not yet implemented");
|
||||
}
|
||||
|
||||
private String readInputLine() {
|
||||
String s;
|
||||
try {
|
||||
s = this.m_in.readLine();
|
||||
} catch (IOException e) {
|
||||
logger.error("readInputLine: some error with reading input. " + e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see meerkat.voting.VotingBooth.UI#votingBegin()
|
||||
*/
|
||||
@Override
|
||||
public void votingBegin() {
|
||||
throw new UnsupportedOperationException("votingBegin: this is actually not a good method for the interface. just run() instead");
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This is a welcome screen for the voter
|
||||
*/
|
||||
private void showWelcomeScreen() {
|
||||
System.out.println("UI screen: Welcome. Press to start");
|
||||
readInputLine();
|
||||
}
|
||||
|
||||
/*
|
||||
* This is a not-initialized error message screen
|
||||
*/
|
||||
private void showNotInitializedScreen() {
|
||||
System.out.println("UI screen: Election params were not yet initialized");
|
||||
System.out.println("UI screen: You must reboot the machine with an admin USB stick plugged in");
|
||||
}
|
||||
|
||||
/*
|
||||
* erase previous encryption from memory
|
||||
*/
|
||||
private void eraseEncryption() {
|
||||
//TODO: should we clean memory 'stronger'?
|
||||
if (m_encryptedBallot != null) {
|
||||
m_encryptedBallot = null;
|
||||
}
|
||||
if (m_ballotSecrets != null) {
|
||||
m_ballotSecrets = null;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* erase previous plaintext from memory
|
||||
*/
|
||||
private void erasePlaintext() {
|
||||
//TODO: should we clean memory 'stronger'?
|
||||
if (m_plaintextBallot != null) {
|
||||
m_plaintextBallot = null;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The voter first needs to choose a channel
|
||||
*/
|
||||
|
||||
private String chooseChannel() {
|
||||
System.out.println("UI screen: Choose channel");
|
||||
System.out.println("UI screen: currently does nothing. Just type any string you like");
|
||||
m_channel = readInputLine();
|
||||
return m_channel;
|
||||
}
|
||||
|
||||
/*
|
||||
* The voting process itself
|
||||
*/
|
||||
private void runVotingFlow() {
|
||||
try {
|
||||
boolean votingOccured = listQuestionsToUserAndGetAnswers();
|
||||
if (votingOccured) {
|
||||
printBallotFlow();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.err.println("UI debug: IO error");
|
||||
}
|
||||
}
|
||||
|
||||
private void printBallotFlow () throws IOException{
|
||||
logger.warn("UI debug: printBallotFlow currently not multithreaded... will fix it...");
|
||||
|
||||
boolean ended = false;
|
||||
while (!ended) {
|
||||
sendBallotToControllerForEncryptionAndWaitForResponse ();
|
||||
sendBallotToPrinterAndWaitToEnd();
|
||||
ended = castOrAudit();
|
||||
eraseEncryption();
|
||||
}
|
||||
erasePlaintext();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This function sends the ballot to the printer
|
||||
*/
|
||||
private void sendBallotToPrinterAndWaitToEnd () {
|
||||
logger.warn("UI debug: sendBallotToPrinterAndWaitToEnd: still not multithreaded...");
|
||||
System.out.println("UI screen: printing ballot wait for printing to end");
|
||||
System.out.println("UI printer: printing ballot");
|
||||
}
|
||||
|
||||
/*
|
||||
* This function sends the audit to the printer
|
||||
*/
|
||||
private void sendAuditToPrinterAndWaitToEnd () {
|
||||
logger.warn("UI debug: sendAuditToPrinterAndWaitToEnd: still not multithreaded...");
|
||||
System.out.println("UI printer: printing audit");
|
||||
}
|
||||
|
||||
private void showPrintingAuditScreen () {
|
||||
System.out.println("UI screen: printing audit wait for printing to end");
|
||||
}
|
||||
|
||||
/*
|
||||
* checks if the voter wants to cast the ballot or audit
|
||||
* returns true if done with this ballot (cast or cancelled)
|
||||
* returns false if still needs the current ballot info
|
||||
*/
|
||||
private boolean castOrAudit () throws IOException{
|
||||
System.out.println("UI screen: (c)ast or (a)udit?");
|
||||
String choice = readInputLine();
|
||||
if (null == choice) {
|
||||
logger.error("castOrAudit: got null as response");
|
||||
throw new IOException();
|
||||
}
|
||||
if (choice.equals("a") || choice.equals("audit")) {
|
||||
logger.debug ("UI debug: voter decided to audit");
|
||||
showPrintingAuditScreen();
|
||||
sendAuditToPrinterAndWaitToEnd ();
|
||||
|
||||
if (!b_allowRecastOfAudittedBallot) {
|
||||
return true;
|
||||
}
|
||||
System.out.println("Do you still want to keep this vote? Type 'y' for casting/auditing the same vote. Type 'n' tp start from scratch");
|
||||
String choice2 = readInputLine();
|
||||
if (null == choice2) {
|
||||
logger.error("castOrAudit: got null as response");
|
||||
throw new IOException ();
|
||||
}
|
||||
if (choice2.equals("y") || choice.equals("yes")) {
|
||||
return false;
|
||||
}
|
||||
else if (choice2.equals("n") || choice.equals("no")) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
throw new IOException ();
|
||||
}
|
||||
}
|
||||
else if (choice.equals("c") || choice.equals("cast") || choice.equals("cancel")) {
|
||||
System.err.println ("UI debug: voter decided to cast vote.");
|
||||
System.out.println("UI screen: wait for printer to finish");
|
||||
System.out.println("UI printer: printing empty space till end of page");
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
throw new IOException ();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* returns true if voting finished successfully
|
||||
* false if cancelled in the middle
|
||||
*/
|
||||
private boolean listQuestionsToUserAndGetAnswers() throws IOException {
|
||||
Voting.PlaintextBallot.Builder ptbb = Voting.PlaintextBallot.newBuilder();
|
||||
|
||||
ptbb.setSerialNumber(m_serialNumber);
|
||||
|
||||
Voting.BallotAnswer answers[] = new Voting.BallotAnswer[a_questions.length];
|
||||
int index = 0;
|
||||
while (index < a_questions.length) {
|
||||
Voting.BallotQuestionNew q = a_questions[index];
|
||||
if (q.getIsMandatory()) {
|
||||
throw new UnsupportedOperationException("listQuestionsToUserAndGetAnswers: question " + index + " is marked as mandatory");
|
||||
}
|
||||
|
||||
showQuestionOnScreen(index, q);
|
||||
System.out.println("UI screen: Enter your answer. You can also type '(b)ack' or '(c)ancel' or '(s)kip");
|
||||
String s = readInputLine();
|
||||
if (null == s) {
|
||||
logger.error("listQuestionsToUserAndGetAnswers: got null as response");
|
||||
throw new IOException("");
|
||||
}
|
||||
|
||||
if ((s.equals("cancel") || s.equals("c")) || (index == 0 && (s.equals("back") || s.equals("b")))) {
|
||||
erasePlaintext();
|
||||
return false;
|
||||
} else if (s.equals("back") || s.equals("b")) {
|
||||
--index;
|
||||
} else if (s.equals("skip") || s.equals("s")) {
|
||||
answers[index] = translateStringAnswerToProtoBufMessageAnswer("");
|
||||
++index;
|
||||
} else {
|
||||
answers[index] = translateStringAnswerToProtoBufMessageAnswer(s);
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
ptbb.addAllAnswers(Arrays.asList(answers));
|
||||
m_plaintextBallot = ptbb.build();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private Voting.BallotAnswer translateStringAnswerToProtoBufMessageAnswer(String s) {
|
||||
Voting.BallotAnswer.Builder bab = Voting.BallotAnswer.newBuilder();
|
||||
StringTokenizer st = new StringTokenizer(s);
|
||||
while (st.hasMoreTokens()) {
|
||||
bab.addAnswer(Integer.parseInt(st.nextToken()));
|
||||
}
|
||||
|
||||
return bab.build();
|
||||
}
|
||||
|
||||
/*
|
||||
* show question on the screen for the voter
|
||||
*/
|
||||
private void showQuestionOnScreen(int i, Voting.BallotQuestionNew q) {
|
||||
|
||||
boolean isText = true;
|
||||
|
||||
if (q.getQuestion().getType() != Voting.UIElementDataType.TEXT
|
||||
|| q.getDescription().getType() != Voting.UIElementDataType.TEXT) {
|
||||
isText = false;
|
||||
}
|
||||
|
||||
for (Voting.UIElement answer : q.getAnswerList()) {
|
||||
if (answer.getType() != Voting.UIElementDataType.TEXT) {
|
||||
isText = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isText) {
|
||||
System.err.println("debug: an element in question " + i + " is not of TEXT type");
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
System.out.println("UI screen: question number " + i);
|
||||
|
||||
System.out.println("Question text: " + bytesToString(q.getQuestion().getData()));
|
||||
System.out.println("Description: " + bytesToString(q.getDescription().getData()));
|
||||
int answerIndex = 0;
|
||||
for (Voting.UIElement answer : q.getAnswerList()) {
|
||||
++answerIndex;
|
||||
System.out.println("Answer " + answerIndex + ": " + bytesToString(answer.getData()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the UTF8 decoding of byte-string data
|
||||
*/
|
||||
private static String bytesToString(ByteString data) {
|
||||
return data.toStringUtf8();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Registering the controller, so we have an object to send encryption queries to
|
||||
*/
|
||||
public void registerVBController(VotingBooth vb) {
|
||||
m_vbController = vb;
|
||||
}
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see meerkat.voting.VotingBooth.UI#commitToEncryptedBallot(meerkat.voting.EncryptedBallot)
|
||||
*/
|
||||
@Override
|
||||
public void commitToEncryptedBallot(Voting.EncryptedBallot encryptedBallot, Voting.BallotSecrets secrets) {
|
||||
try {
|
||||
a_queue.put (VBMessage.newControllerResponse(encryptedBallot, secrets));
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
System.err.println ("Interrupted in VotingBoothToyConsoleUI.commitToEncryptedBallot");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void sendBallotToControllerForEncryptionAndWaitForResponse () {
|
||||
|
||||
class TickerTask extends TimerTask {
|
||||
private VotingBoothToyConsoleUI m_ui;
|
||||
public TickerTask (VotingBoothToyConsoleUI ui) {
|
||||
m_ui = ui;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
m_ui.tick();
|
||||
}
|
||||
}
|
||||
|
||||
eraseEncryption();
|
||||
System.out.println("UI screen: Please wait for encryption");
|
||||
m_vbController.submitBallot(m_plaintextBallot);
|
||||
Timer timer = new Timer();
|
||||
timer.scheduleAtFixedRate(new TickerTask(this), new Date(), m_waitForControllerMillisecTimeout);
|
||||
|
||||
while (m_encryptedBallot == null)
|
||||
{
|
||||
VBMessage msg;
|
||||
try {
|
||||
msg = a_queue.take();
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
System.err.println("VotingBoothToyConsoleUI.sendBallotToControllerForEncryptionAndWaitForResponse interrupted!");
|
||||
return;
|
||||
}
|
||||
if (msg.isEmptyMessage()) {
|
||||
System.out.print(".");
|
||||
}
|
||||
else {
|
||||
m_encryptedBallot = msg.getEncryptedBallot();
|
||||
if (m_encryptedBallot.getSerialNumber() != m_plaintextBallot.getSerialNumber())
|
||||
{
|
||||
eraseEncryption();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("sendBallotToControllerForEncryptionAndWaitForResponse: Received EncryptedBallot");
|
||||
timer.cancel();
|
||||
timer.purge();
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package meerkat.voting;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import meerkat.protobuf.Crypto.SignatureType;
|
||||
import meerkat.protobuf.Crypto.SignatureVerificationKey;
|
||||
import meerkat.protobuf.Voting.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class VotingBoothToyDemoRun {
|
||||
final static Logger logger = LoggerFactory.getLogger(VotingBoothToyDemoRun.class);
|
||||
|
||||
static int N_SIGNATURE_VERIFICATION_KEYS = 3;
|
||||
|
||||
public static void main (String[] args)
|
||||
{
|
||||
VotingBoothToy vbController = new VotingBoothToy ();
|
||||
VotingBoothToyConsoleUI ui = new VotingBoothToyConsoleUI();
|
||||
|
||||
logger.debug("Running with {} args", args.length);
|
||||
vbController.registerUI (ui);
|
||||
ui.registerVBController(vbController);
|
||||
|
||||
|
||||
|
||||
BoothParams boothParams = generateDemoBoothParams();
|
||||
ElectionParams electionParams = generateDemoElectionParams();
|
||||
|
||||
vbController.init(electionParams, boothParams);
|
||||
ui.init(electionParams);
|
||||
|
||||
Thread controllerThread = new Thread(vbController);
|
||||
Thread uiThread = new Thread(ui);
|
||||
|
||||
controllerThread.start();
|
||||
uiThread.start();
|
||||
}
|
||||
|
||||
|
||||
public static BoothParams generateDemoBoothParams () {
|
||||
BoothParams.Builder bpb = BoothParams.newBuilder();
|
||||
SignatureType signatureType = SignatureType.ECDSA;
|
||||
for (int i = 0; i < N_SIGNATURE_VERIFICATION_KEYS; ++i) {
|
||||
SignatureVerificationKey verifiationKey = SignatureVerificationKey.newBuilder()
|
||||
.setType(signatureType)
|
||||
.setData(ByteString.copyFrom(new byte[0]))
|
||||
.build();
|
||||
bpb.addPscVerificationKeys(i, verifiationKey);
|
||||
}
|
||||
return bpb.build();
|
||||
|
||||
}
|
||||
|
||||
public static BallotQuestionNew generateBallotQuestion(String questionStr, String descriptionStr, String[] answers) {
|
||||
UIElement question = UIElement.newBuilder()
|
||||
.setType(UIElementDataType.TEXT)
|
||||
.setData(stringToBytes(questionStr))
|
||||
.build();
|
||||
|
||||
UIElement description = UIElement.newBuilder()
|
||||
.setType(UIElementDataType.TEXT)
|
||||
.setData(stringToBytes(descriptionStr))
|
||||
.build();
|
||||
|
||||
BallotQuestionNew.Builder bqb = BallotQuestionNew.newBuilder();
|
||||
bqb.setIsMandatory(false);
|
||||
bqb.setQuestion(question);
|
||||
bqb.setDescription(description);
|
||||
for (String answerStr : answers) {
|
||||
UIElement answer = UIElement.newBuilder()
|
||||
.setType(UIElementDataType.TEXT)
|
||||
.setData(stringToBytes(answerStr))
|
||||
.build();
|
||||
bqb.addAnswer(answer);
|
||||
}
|
||||
|
||||
return bqb.build();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static ByteString stringToBytes (String s) {
|
||||
return ByteString.copyFromUtf8(s);
|
||||
}
|
||||
|
||||
public static ElectionParams generateDemoElectionParams () {
|
||||
String[] answers1 = {"Blue", "Red", "Green", "Purple"};
|
||||
BallotQuestionNew question1 = generateBallotQuestion("What is your favorite color?", "Pick one answer", answers1);
|
||||
|
||||
String[] answers2 = {"Miranda Kerr", "Doutzen Kroes", "Moran Atias", "Roslana Rodina", "Adriana Lima"};
|
||||
BallotQuestionNew question2 = generateBallotQuestion("Which model do you like", "Mark as many as you want", answers2);
|
||||
|
||||
String[] answers3 = {"Clint Eastwood", "Ninja", "Sonic", "Tai-chi", "Diablo"};
|
||||
BallotQuestionNew question3 = generateBallotQuestion("Good name for a cat", "Pick the best one", answers3);
|
||||
|
||||
ElectionParams.Builder epb = ElectionParams.newBuilder();
|
||||
epb.setTrusteeSignatureThreshold(5);
|
||||
epb.addQuestion(question1);
|
||||
epb.addQuestion(question2);
|
||||
epb.addQuestion(question3);
|
||||
|
||||
return epb.build();
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue