diff --git a/voting-booth/src/main/java/meerkat/voting/VBMessage.java b/voting-booth/src/main/java/meerkat/voting/VBMessage.java new file mode 100644 index 0000000..0555e8f --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/VBMessage.java @@ -0,0 +1,52 @@ +package meerkat.voting; + +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; + + public static VBMessage newTick () { + VBMessage retVal = new VBMessage(); + retVal.m_type = VBMessageType.VB_TICK; + return retVal; + } + + 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) { + VBMessage retVal = new VBMessage(); + retVal.m_type = VBMessageType.VB_CONTROLLER_RESPONSE; + retVal.m_encryptedBallot = encryptedBallot; + return retVal; + } + + public EncryptedBallot getEncryptedBallot () { + assert m_type == VBMessageType.VB_CONTROLLER_RESPONSE; + return m_encryptedBallot; + } + + public PlaintextBallot getPlaintextBallot () { + assert m_type == VBMessageType.VB_ENCRYPTION_QUERY; + return m_plaintextBallot; + } + + public boolean isEmptyMessage () { + return (m_type == VBMessageType.VB_TICK); + } +} diff --git a/voting-booth/src/main/java/meerkat/voting/VotingBoothToy.java b/voting-booth/src/main/java/meerkat/voting/VotingBoothToy.java new file mode 100644 index 0000000..1f806ec --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/VotingBoothToy.java @@ -0,0 +1,161 @@ +package meerkat.voting; + +import meerkat.protobuf.Voting.BallotAnswer; +import meerkat.protobuf.Voting.BallotAnswerTranslationTable; +import meerkat.protobuf.Voting.BallotQuestion; +import meerkat.protobuf.Voting.BallotSecrets; +import meerkat.protobuf.Voting.BoothParams; +import meerkat.protobuf.Voting.ElectionParams; +import meerkat.protobuf.Voting.PlaintextBallot; +import meerkat.protobuf.Crypto.EncryptionPublicKey; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.List; + + + +public class VotingBoothToy implements VotingBooth, Runnable { + + //private ElectionParams m_electionParams; + private EncryptionPublicKey m_ballotEncryptionKey; + private List l_questions; + private BallotQuestion a_questions[]; + private BallotAnswer a_answers[]; + private BallotAnswerTranslationTable m_answerTranslationTable; + + private BoothParams m_boothParams; + private VotingBooth.UI m_ui; + + + public VotingBoothToy () { + } + + public void registerUI (UI ui) { + m_ui = ui; + } + + public void run () { + + } + + /* (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_electionParams = globalParams; + this.m_ballotEncryptionKey = globalParams.getBallotEncryptionKey(); + this.l_questions = globalParams.getQuestionsList(); + + this.m_answerTranslationTable = globalParams.getAnswerTranslationTable(); + + this.m_boothParams = boothParams; + + } + + public void registerVBUI (VotingBooth.UI ui) + { + m_ui = ui; + } + /* (non-Javadoc) + * @see meerkat.voting.VotingBooth#submitBallot(meerkat.protobuf.Voting.PlaintextBallot) + */ + @Override + public void submitBallot(PlaintextBallot ballot) { + System.err.println ("debug VB: submit ballot."); + // encryptBallot (PlaintextBallot) + + } + + /* (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 PlaintextBallot { + uint64 serialNumber = 1; // Ballot serial number + + repeated BallotAnswer answers = 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; + +} +*/ \ No newline at end of file diff --git a/voting-booth/src/main/java/meerkat/voting/VotingBoothToyConsoleUI.java b/voting-booth/src/main/java/meerkat/voting/VotingBoothToyConsoleUI.java new file mode 100644 index 0000000..b4cc65f --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/VotingBoothToyConsoleUI.java @@ -0,0 +1,305 @@ +/** + * + */ +package meerkat.voting; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Date; +import java.util.List; +import java.util.StringTokenizer; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ArrayBlockingQueue; + +import com.google.protobuf.ByteString; + +import meerkat.protobuf.Voting.*; +import meerkat.voting.VotingBooth.UI; + +/** + * @author hai + * + */ +public class VotingBoothToyConsoleUI implements UI, Runnable { + + private BufferedReader m_in; + private VotingBooth m_vbController; + //private SharedPlaintextBallotMessage m_sharedPlaintext; + //private SharedEncryptedBallotMessage m_sharedEncrypted; + private ArrayBlockingQueue a_queue; + static private int m_queueSize = 5; + private BallotQuestion a_questions[]; + private int m_serialNumber; + private PlaintextBallot m_plaintextBallot; + private EncryptedBallot m_encryptedBallot; + private int m_waitForControllerMillisecTimeout = 10; + + + public VotingBoothToyConsoleUI () { + a_queue = new ArrayBlockingQueue (m_queueSize, true); + } + + public VotingBoothToyConsoleUI(ElectionParams globalParams) { + m_serialNumber = 0; + List l_questions = globalParams.getQuestionsList(); + a_questions = new BallotQuestion[l_questions.size()]; + m_in = new BufferedReader(new InputStreamReader(System.in)); + + int i = 0; + for (BallotQuestion q: l_questions) { + a_questions[i] = q; + ++i; + } + } + + /* (non-Javadoc) + * @see meerkat.voting.VotingBooth.UI#votingBegin() + */ + @Override + public void votingBegin() { + System.err.println ("UI debug: preparing console UI for a new user."); + System.out.println ("UI screen: Welcome. Press to start"); + readInputLine(); + + ++ m_serialNumber; + boolean votingOccured = listQuestionsToUserAndGetAnswers (); + + if (! votingOccured) + { + // cancel vote; + return; + } + else { + sendBallotToControllerForEncryptionAndWaitForResponse (); + } + } + + + /* (non-Javadoc) + * @see meerkat.voting.VotingBooth.UI#commitToEncryptedBallot(meerkat.voting.EncryptedBallot) + */ + @Override + public void commitToEncryptedBallot(EncryptedBallot encryptedBallot) { + try { + a_queue.put (VBMessage.newControllerResponse(encryptedBallot)); + } + catch (InterruptedException e) { + System.err.println ("Interrupted in VotingBoothToyConsoleUI.commitToEncryptedBallot"); + } + } + + /* (non-Javadoc) + * @see meerkat.voting.VotingBooth.UI#castVote() + */ + @Override + public void castVote() { + System.err.println ("UI debug: voter decided to cast vote."); + System.out.println ("UI printer: print empty space until end of page"); + } + + /* (non-Javadoc) + * @see meerkat.voting.VotingBooth.UI#auditVote(meerkat.protobuf.Voting.BallotSecrets) + */ + @Override + public void auditVote(BallotSecrets ballotSecrets) { + // audit = calcAudit (ballotSecrets) + // sendToPrinter (audit) + System.err.println ("UI debug: voter decided to audit vote."); + System.out.println ("UI printer: here we should print audit of ballot"); + + } + + public void run () { + System.out.println("UI screen: Initializing the magic"); + + System.out.println("UI screen: press something to start the system"); + readInputLine(); + + while (true) { + votingBegin(); + + if (m_encryptedBallot != null) { + + printBallot(); + + castOrAudit (); + + } + } + } + + private void castOrAudit () { + + // fix the flow here!! + // keeping the same vote should return for a new encryption + + System.out.println("UI screen: printing ended. Do you want to (c)ast the ballot or (a)udit?"); + + boolean keepThisVote = true; + String s = readInputLine(); + if (s.equals("c") || s.equals("cancel") || s.equals("cast")) { + castVote(); + keepThisVote = false; + } + else if (s.equals("a") || s.equals("audit")) { + auditVote(); + 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"); + if (s.equals("y") || s.equals("yes")) { + // + } + else if (s.equals("n") || s.equals("no")) { + keepThisVote = false; + } + else { + throw new IOException(); + } + } + else { + throw new IOException(); + } + } + + public void registerVBController (VotingBooth vb) + { + m_vbController = vb; + //m_sharedPlaintext = sharedPlaintext; + //m_sharedEncrypted = sharedEncrypted; + } + + /* + * returns true if voting finished successfully + * false if cancelled in the middle + */ + private boolean listQuestionsToUserAndGetAnswers() + { + PlaintextBallot.Builder ptbb = PlaintextBallot.newBuilder(); + + ptbb.setSerialNumber(m_serialNumber); + + int index = 0; + while (index < a_questions.length) { + BallotQuestion q = a_questions[index]; + printQuestion(index, q); + System.out.println("UI screen: Enter your answer. You can also type 'back' or 'cancel'"); + String s = readInputLine(); + + if (s.equals("cancel") || (index == 0 && s.equals("back"))) { + m_plaintextBallot = null; + return false; + } + + if (s.equals("back")) { + --index; + continue; + } + + BallotAnswer answer = translateStringAnswerToProtoBufMessageAnswer (s); + ptbb.setAnswers(index, answer); + } + + m_plaintextBallot = ptbb.build(); + return true; + } + + + private String readInputLine () { + String s; + try { + s = this.m_in.readLine(); + } + catch (IOException e) { + System.err.println("UI debug: VotingBegin(): some error with reading input. " + e); + return null; + } + + return s; + } + + private String getHexData (ByteString data) { + String s = ""; + for (Byte b : data) { + s += "0123456789ABCDEF".charAt((int)b / 16); + s += "0123456789ABCDEF".charAt((int)b % 16); + } + return s; + } + + private void printHexData (ByteString data) { + String s = getHexData(data); + System.out.println(s); + } + + private void printQuestion (int i, BallotQuestion q) { + System.out.println("UI screen: question number " + i); + System.out.println(q.getData()); + printHexData (q.getData()); + } + + private BallotAnswer translateStringAnswerToProtoBufMessageAnswer (String s) { + BallotAnswer.Builder bab = BallotAnswer.newBuilder(); + StringTokenizer st = new StringTokenizer(s); + int index = 0; + while (st.hasMoreTokens()) { + ++index; + bab.setAnswer(index, Integer.parseInt(st.nextToken())); + } + + BallotAnswer ba = bab.build(); + return ba; + } + + public void tick () { + // adds a 'tick'. If queue is full, do nothing + a_queue.add (VBMessage.newTick()); + } + + 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(); + } + } + + System.out.println("UI screen: Please wait for encryption"); + m_vbController.submitBallot(m_plaintextBallot); + m_encryptedBallot = null; + Timer timer = new Timer(); + timer.scheduleAtFixedRate(new TickerTask(this), new Date(), m_waitForControllerMillisecTimeout); + + while (m_encryptedBallot == null) + { + VBMessage msg = null; + 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()) + { + m_encryptedBallot = null; + } + } + } + + System.out.println("\nUI debug: Received EncryptedBallot"); + timer.cancel(); + timer.purge(); + + } +}