This repository has been archived on 2022-08-01. You can view files and clone it, but cannot push or open issues or pull requests.
DEMOS2/static/js/event_vote.js

425 lines
12 KiB
JavaScript

var dialogOpen = false;
function showDialogWithText(titleTxt, bodyTxt) {
var modalDialog = $('#modalDialog');
var title = modalDialog.find('.modal-title');
var body = modalDialog.find('.modal-body');
title.text(titleTxt);
var p = document.createElement("p");
p.innerHTML = bodyTxt;
body.empty();
body.append( p );
if(!dialogOpen) {
modalDialog.modal('toggle');
dialogOpen = true;
}
}
// This should stop people ticking more than the maximum permitted
function updateCheckboxInteractivity() {
var inputs = $("label input[type=checkbox]");
if(selectedCount === MAX_SELECTIONS) {
inputs.each(function() {
var input = $(this);
if(!input.prop('checked')){
input.prop('disabled', true);
}
});
} else {
inputs.each(function() {
var input = $(this);
if(!input.prop('checked')) {
input.prop('disabled', false);
}
});
}
}
$("label input[type=checkbox]").change(function() {
// Increment the selectedCount counter if a box has been checked
if(this.checked) {
selectedCount += 1;
} else {
selectedCount -= 1;
// Just incase this falls below zero to avoid any nasty bugs
if(selectedCount < 0) {
selectedCount = 0;
}
}
updateCheckboxInteractivity();
});
function isVotingInputValid() {
var valid = true;
// First establish if the user's selection count is valid
if(!(selectedCount >= MIN_SELECTIONS && selectedCount <= MAX_SELECTIONS)) {
valid = false;
}
if(selectedCount < MAX_SELECTIONS) {
valid = false;
}
// This will highlight when people haven't selected enough options
if(!valid) {
let errText = "You've only selected " + selectedCount;
if(selectedCount > 1) {
errText += " options.";
} else {
errText = " You haven't selected any options.";
}
errText += " The minimum number you need to select is " + MIN_SELECTIONS + " and the maximum is "
+ MAX_SELECTIONS + ". Please go back and correct this.";
let titleTxt = 'Voting Error';
showDialogWithText(titleTxt, errText);
return;
}
return valid;
}
// Generates a blank vote as a string using the binary encoding scheme
function genBlankVote() {
var vote = "";
for(var i = 0; i < OPTION_COUNT; i++) {
vote += "0";
if (i !== (OPTION_COUNT - 1)) {
vote += ",";
}
}
return vote;
}
var progressBar = document.getElementById("progress-bar");
// Based on the user's vote in the current poll, this generates a ballot which
// does not leak information about how many options the user has selected
function generateBallot() {
// Elliptic curve cryptography params used for encryption of encrypted vote
// fragments
var ctx = new CTX("BN254CX");
var n = new ctx.BIG();
var g1 = new ctx.ECP();
var g2 = new ctx.ECP2();
var parameter = $('#event-param').val();
var tempParams = JSON.parse(JSON.parse(parameter).crypto);
//copying the values
n.copy(tempParams.n);
g1.copy(tempParams.g1);
g2.copy(tempParams.g2);
var params = {
n:n,
g1:g1,
g2:g2
};
var tempPK = JSON.parse($('#comb_pk').val());
var pk = new ctx.ECP(0);
pk.copy(tempPK.PK);
// Collect together the unencrypted votes (which correspond to selected options)
var checkboxInputs = $("label input[type=checkbox]");
var unencryptedVotes = [];
checkboxInputs.each(function() {
var checkbox = $(this);
// Push the selected option values (ones that have been checked) to an array
if(checkbox.prop('checked')) {
unencryptedVotes.push(checkbox.val());
}
});
// If there is a dif between the num selected and the max allowed, push blank votes to the array to pad this
// to prevent information leakage
if(unencryptedVotes.length < MAX_SELECTIONS) {
let blankVotesToPush = MAX_SELECTIONS - unencryptedVotes.length;
for(let i = 0; i < blankVotesToPush; i++) {
unencryptedVotes.push(genBlankVote());
}
}
// Encrypt all of the votes for this ballot
var encryptedVotes = [];
unencryptedVotes.forEach(function(unencryptedVote) {
var encFragments = [];
// Encrypt each fragment of the unencrypted vote
unencryptedVote.split(',').forEach(function(fragment) {
var cipher = encrypt(params, pk, parseInt(fragment));
// Store C1 and C2 from the cipher in the fragment
var c1Bytes = [];
cipher.C1.toBytes(c1Bytes);
var c2Bytes = [];
cipher.C2.toBytes(c2Bytes);
encFragments.push({
C1 : c1Bytes.toString(),
C2 : c2Bytes.toString()
});
});
// Store all fragments in a single 'encrypted vote'
encryptedVotes.push({
fragments: encFragments
});
});
return {
encryptedVotes: encryptedVotes
};
}
$('#gen-ballots-btn').click(function() {
// Ensure that the user selections are valid
if(isVotingInputValid()) {
// Hide the button
$(this).toggleClass('hidden');
// Inject the description progress bar which can then be updated by the encrypt btn
$('#progress-bar-description').toggleClass('hidden');
$('#progress-bar-container').toggleClass('hidden');
setTimeout(generateBallotsAndShowUsr, 25);
}
});
function voteSuccessfullyReceived() {
let titleTxt = 'Vote Successfully Received';
let bodyText = "Thank you for voting!";
if(POLL_NUM !== POLL_COUNT) {
bodyText += " You can vote on the next poll by closing down this dialog and clicking 'Next Poll'.";
}
showDialogWithText(titleTxt, bodyText);
// Update the dialog's btns
$('#cancelVoteBtn').addClass("hidden");
$('#closeDialogBtn').removeClass("hidden");
}
var CSRF = $( "input[name='csrfmiddlewaretoken']" ).val();
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
function sendBallotToServer(selection, ballot, ballotSelectionDialog) {
// Use ajax to send the selected ballot to server
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", CSRF);
}
}
});
$.ajax({
type : "POST",
url : window.location,
data : JSON.stringify({ selection: selection, ballot: ballot}),
success : function() {
voteSuccessfullyReceived();
}
});
// Update the client to inform the user that the vote is being processed
showDialogWithText("Please Wait", "Processing Vote. Please wait...");
}
var bytestostring = function(b) {
var s = "";
var len = b.length;
var ch;
for (var i = 0; i < len; i++) {
ch = b[i];
s += ((ch >>> 4) & 15).toString(16);
s += (ch & 15).toString(16);
}
return s;
};
var stringtobytes = function(s) {
var b = [];
for (var i = 0; i < s.length; i++) {
b.push(s.charCodeAt(i));
}
return b;
};
function SHA256Hash(bytes, toStr) {
var ctx = new CTX();
var R = [];
var H = new ctx.HASH256();
H.process_array(bytes);
R = H.hash();
if (R.length === 0) {
return null;
}
if(toStr) {
// If toStr is true we return the stringified version of the bytes of the hash
return bytestostring(R);
} else {
// If toStr is false we return the bytes of the hash
return R;
}
}
// FAO Ben: Called once the ballot has been sent to the back-end and dialog has closed
function onAfterBallotSend() {
// TODO: FAO Ben: Implement QR func here.
// TODO: Currently, there is a dialog already implemented in the event_vote.html page which is
// TODO: used for voting error information but could be used to display the QR code using JS in
// TODO: a similar way that showBallotChoiceDialog does.
}
function processBallotSelection(selection, selectedBallot, selectedBallotHash, dialog, successFn) {
// Dispatch the ballot to the server
sendBallotToServer(selection, selectedBallot, dialog);
// Call the successfn currently with the selection hash but this may not be needed
successFn(selectedBallotHash);
}
function showBallotChoiceDialog(ballotA, ballotB) {
// Output hashes of the 2 ballots
const BALLOT_A_HASH = SHA256Hash(stringtobytes(JSON.stringify(ballotA)), true);
const BALLOT_B_HASH = SHA256Hash(stringtobytes(JSON.stringify(ballotB)), true);
// With the ballots and their hashes generated, we can display the ballot choice dialog
var modalDialog = $('#modalDialog');
var title = modalDialog.find('.modal-title');
var body = modalDialog.find('.modal-body');
body.empty();
title.text('Please Select a Ballot');
// Generate the body of the dialog which consists of a button for A and for B as well as their hashes
var choiceGroupDiv = document.createElement('div');
choiceGroupDiv.setAttribute('class', 'choice-group');
var btnChoiceA = document.createElement('a');
btnChoiceA.setAttribute('id', 'choice-A');
btnChoiceA.setAttribute('class', 'btn btn-sq btn-primary');
btnChoiceA.innerHTML = 'A';
choiceGroupDiv.append(btnChoiceA);
var btnChoiceB = document.createElement('a');
btnChoiceB.setAttribute('id', 'choice-B');
btnChoiceB.setAttribute('class', 'btn btn-sq btn-warning choice');
btnChoiceB.innerHTML = 'B';
choiceGroupDiv.append(btnChoiceB);
// ----------------------------------------------
var hashGroupDiv = document.createElement('div');
var br = document.createElement('br');
hashGroupDiv.append( br );
var hashA = document.createElement("span");
hashA.innerHTML = "Hash A: " + BALLOT_A_HASH;
hashGroupDiv.append( hashA );
var br2 = document.createElement('br');
hashGroupDiv.append( br2 );
var hashB = document.createElement("span");
hashB.innerHTML = "Hash B: " + BALLOT_B_HASH;
hashGroupDiv.append( hashB );
// -----------------------------------------------
body.append(choiceGroupDiv);
body.append(hashGroupDiv);
// Prepare the appropriate dialog buttons
$('#cancelVoteBtn').removeClass("hidden");
$('#closeDialogBtn').removeClass("hidden").addClass("hidden");
if(!dialogOpen) {
modalDialog.modal('toggle');
dialogOpen = true;
}
// Register callback functions for the selection of either A or B
$('#choice-A').click(function(e) {
processBallotSelection("A", ballotA, BALLOT_A_HASH, modalDialog, onAfterBallotSend);
});
$('#choice-B').click(function(e) {
processBallotSelection("B", ballotB, BALLOT_B_HASH, modalDialog, onAfterBallotSend);
});
}
function generateBallotB(ballotA) {
var ballotB = generateBallot();
progressBar.setAttribute("style", "width: 100%;");
showBallotChoiceDialog(ballotA, ballotB);
}
function generateBallotsAndShowUsr() {
// Generate Ballot A and Ballot B to be displayed to the user
// This fn starts the process
var ballotA = generateBallot();
// Update the progress bar once the generation has completed
progressBar.setAttribute("style", "width: 50%;");
// This delay allows the execution thread to update the above CSS on the progress bar
setTimeout(function () {
generateBallotB(ballotA);
}, 150);
}
$('#modalDialog').on('hide.bs.modal', function (e) {
var titleText = $(this).find('.modal-title').text();
if(titleText.indexOf("Received") > -1) {
// Update page to reflect the fact that a vote has taken place
location.reload();
} else {
// Reset poll voting to allow user to vote again
progressBar.setAttribute("style", "width: 0%;");
$('#gen-ballots-btn').toggleClass("hidden");
$('#progress-bar-description').toggleClass('hidden');
$('#progress-bar-container').toggleClass('hidden');
var inputs = $("label input[type=checkbox]");
inputs.each(function () {
var input = $(this);
input.prop('checked', false);
input.prop('disabled', false);
});
selectedCount = 0;
}
dialogOpen = false;
});