Updated the voting and event decryption pages. On the voting page, added the ability to vote by selecting checkboxes instead of a drop down select menu. This adds flexibility at event creation to specify a wide range of min and max option selections for polls. On the event decryption page, SK validation was added based on the stored trustee PK as well as a dialog to display any validation errors. Most of the updates took place behind the scenes at the backend.

This commit is contained in:
vince0656 2018-07-13 17:19:35 +01:00
parent 3fd9173666
commit 745fdf06b1
10 changed files with 503 additions and 152 deletions

View file

@ -51,7 +51,7 @@ class Event(models.Model):
try: try:
EID_json = json.loads(self.EID) EID_json = json.loads(self.EID)
EID_crypto_str = EID_json['crypto'] EID_crypto_str = EID_json['crypto']
return json.loads(EID_crypto_str) return json.dumps(json.loads(EID_crypto_str))
except ValueError: except ValueError:
return "None - Event not Initialised" return "None - Event not Initialised"
@ -213,8 +213,13 @@ class EncryptedVote(models.Model):
ballot = models.ForeignKey(Ballot, on_delete=models.CASCADE, related_name="encrypted_vote") ballot = models.ForeignKey(Ballot, on_delete=models.CASCADE, related_name="encrypted_vote")
class CombinedEncryptedVote(models.Model):
ballot = models.ForeignKey(Ballot, on_delete=models.CASCADE, related_name="comb_encrypted_vote")
class VoteFragment(models.Model): class VoteFragment(models.Model):
encrypted_vote = models.ForeignKey(EncryptedVote, on_delete=models.CASCADE, related_name="fragment") encrypted_vote = models.ForeignKey(EncryptedVote, on_delete=models.CASCADE, related_name="fragment", null=True)
comb_encrypted_vote = models.ForeignKey(CombinedEncryptedVote, on_delete=models.CASCADE, related_name="fragment", null=True)
cipher_text_c1 = models.CharField(max_length=4096) cipher_text_c1 = models.CharField(max_length=4096)
cipher_text_c2 = models.CharField(max_length=4096) cipher_text_c2 = models.CharField(max_length=4096)

View file

@ -7,7 +7,7 @@ from celery import task
from django.conf import settings from django.conf import settings
from allauthdemo.polls.models import AccessKey, Ballot, CombinedBallot, PartialBallotDecryption from allauthdemo.polls.models import AccessKey, Ballot, CombinedBallot, PartialBallotDecryption, EncryptedVote, CombinedEncryptedVote, VoteFragment
from .crypto_rpc import param, combpk, add_ciphers, get_tally from .crypto_rpc import param, combpk, add_ciphers, get_tally
@ -58,6 +58,7 @@ def email_trustees_dec(event):
trustee.send_email(email_subject, email_body) trustee.send_email(email_subject, email_body)
def get_email_sign_off(): def get_email_sign_off():
sign_off = str("") sign_off = str("")
sign_off += "\n\nPlease note: This email address is not monitored so please don't reply to this email.\n\n" sign_off += "\n\nPlease note: This email address is not monitored so please don't reply to this email.\n\n"
@ -66,6 +67,7 @@ def get_email_sign_off():
return sign_off return sign_off
''' '''
Combines all of the voter ballots for a poll option into a single 'CombinedBallot' Combines all of the voter ballots for a poll option into a single 'CombinedBallot'
''' '''
@ -83,7 +85,7 @@ def combine_ballots(polls):
frags_c2 = list() frags_c2 = list()
for ballot in ballots: for ballot in ballots:
enc_vote = ballot.encrypted_vote.get() enc_vote = ballot.comb_encrypted_vote.get()
if enc_vote is not None: if enc_vote is not None:
fragments = enc_vote.fragment.all() fragments = enc_vote.fragment.all()
@ -102,6 +104,34 @@ def combine_ballots(polls):
cipher_text_c1=combined_cipher['C1'], cipher_text_c1=combined_cipher['C1'],
cipher_text_c2=combined_cipher['C2']) cipher_text_c2=combined_cipher['C2'])
@task()
def combine_encrypted_votes(voter, poll):
poll_options_count = poll.options.all().count()
ballot = Ballot.objects.get_or_create(voter=voter, poll=poll)[0]
e_votes = EncryptedVote.objects.filter(ballot=ballot)
CombinedEncryptedVote.objects.filter(ballot=ballot).delete()
comb_e_vote = CombinedEncryptedVote.objects.create(ballot=ballot)
for i in range(poll_options_count):
frags_c1 = list()
frags_c2 = list()
for e_vote in e_votes:
fragments = e_vote.fragment.all()
frags_c1.append(fragments[i].cipher_text_c1)
frags_c2.append(fragments[i].cipher_text_c2)
ciphers = {
'c1s': frags_c1,
'c2s': frags_c2
}
combined_cipher = add_ciphers(ciphers)
VoteFragment.objects.create(comb_encrypted_vote=comb_e_vote,
cipher_text_c1=combined_cipher['C1'],
cipher_text_c2=combined_cipher['C2'])
@task() @task()
def create_ballots(event): def create_ballots(event):
voters = event.voters.all() voters = event.voters.all()

View file

@ -11,11 +11,11 @@ from django.views import generic
from django.conf import settings from django.conf import settings
from .forms import PollForm, OptionFormset, VoteForm, EventSetupForm, EventEditForm from .forms import PollForm, OptionFormset, VoteForm, EventSetupForm, EventEditForm
from .models import Event, Poll, Ballot, EncryptedVote, TrusteeKey, PartialBallotDecryption, CombinedBallot from .models import Event, Poll, Ballot, EncryptedVote, TrusteeKey, PartialBallotDecryption, CombinedBallot, VoteFragment
from allauthdemo.auth.models import DemoUser from allauthdemo.auth.models import DemoUser
from .tasks import email_trustees_prep, update_EID, generate_combpk, event_ended, create_ballots from .tasks import email_trustees_prep, update_EID, generate_combpk, event_ended, create_ballots
from .tasks import create_ballots_for_poll, email_voters_vote_url, combine_decryptions_and_tally from .tasks import create_ballots_for_poll, email_voters_vote_url, combine_decryptions_and_tally, combine_encrypted_votes
from .utils.EventModelAdaptor import EventModelAdaptor from .utils.EventModelAdaptor import EventModelAdaptor
@ -153,39 +153,35 @@ def event_vote(request, event_id, poll_id):
cant_vote_reason = "The event either isn't ready for voting or it has expired and therefore you cannot vote." cant_vote_reason = "The event either isn't ready for voting or it has expired and therefore you cannot vote."
if request.method == "POST": if request.method == "POST":
ballot = Ballot.objects.get_or_create(voter=email_key[0].user, poll=poll)[0] data = json.loads(request.POST.lists()[0][0])
ballot_json = data['ballot']
encrypted_votes_json = ballot_json['encryptedVotes']
# Before storing the encrypted votes, we need the voter's ballot
ballot, created = Ballot.objects.get_or_create(voter=email_key[0].user, poll=poll)
EncryptedVote.objects.filter(ballot=ballot).delete()
for e_vote in encrypted_votes_json:
# Will store the fragments of the encoding scheme that define the vote # Will store the fragments of the encoding scheme that define the vote
encrypted_vote = EncryptedVote.objects.get_or_create(ballot=ballot)[0] encrypted_vote = EncryptedVote.objects.create(ballot=ballot)
fragments_json = e_vote['fragments']
# Clear any existing fragments - a voter changing their vote for fragment in fragments_json:
encrypted_vote.fragment.all().delete() VoteFragment.objects.create(encrypted_vote=encrypted_vote,
cipher_text_c1=fragment['C1'],
# Add in the new ciphers cipher_text_c2=fragment['C2'])
fragment_count = int(request.POST['vote_frag_count'])
for i in range(fragment_count):
i_str = str(i)
cipher_c1 = request.POST['cipher_c1_frag_' + i_str]
cipher_c2 = request.POST['cipher_c2_frag_' + i_str]
encrypted_vote.fragment.create(encrypted_vote=encrypted_vote,
cipher_text_c1=cipher_c1,
cipher_text_c2=cipher_c2)
ballot.cast = True ballot.cast = True
ballot.save() ballot.save()
combine_encrypted_votes.delay(email_key[0].user, poll)
if next_poll_uuid: if next_poll_uuid:
return HttpResponseRedirect(reverse('polls:event-vote', kwargs={'event_id': event.uuid, return HttpResponseRedirect(reverse('polls:event-vote', kwargs={'event_id': event.uuid,
'poll_id': next_poll_uuid}) 'poll_id': next_poll_uuid})
+ "?key=" + email_key_str) + "?key=" + email_key_str)
else:
# The user has finished voting in the event
success_msg = 'You have successfully cast your vote(s)!'
messages.add_message(request, messages.SUCCESS, success_msg)
return HttpResponseRedirect(reverse("user_home")) return HttpResponse('Voted Successfully!')
return render(request, "polls/event_vote.html", return render(request, "polls/event_vote.html",
{ {
@ -273,16 +269,20 @@ def event_trustee_decrypt(request, event_id):
if access_key: if access_key:
email_key = event.keys.filter(key=access_key) email_key = event.keys.filter(key=access_key)
trustee = email_key[0].user
if email_key.exists() and event.users_trustees.filter(email=email_key[0].user.email).exists(): if email_key.exists() and event.users_trustees.filter(email=trustee.email).exists():
if PartialBallotDecryption.objects.filter(event=event, user=email_key[0].user).count() == event.total_num_opts(): if PartialBallotDecryption.objects.filter(event=event, user=trustee).count() == event.total_num_opts():
warning_msg = 'You have already provided your decryption key for this event - Thank You' warning_msg = 'You have already provided your decryption key for this event - Thank You'
messages.add_message(request, messages.WARNING, warning_msg) messages.add_message(request, messages.WARNING, warning_msg)
return HttpResponseRedirect(reverse("user_home")) return HttpResponseRedirect(reverse("user_home"))
elif request.method == "GET": elif request.method == "GET":
# Get the Trustee's original PK - used in the template for SK validation
trustee_pk = TrusteeKey.objects.get(event=event, user=trustee).key
# Gen a list of ciphers from the combined ballots for every opt of every poll # Gen a list of ciphers from the combined ballots for every opt of every poll
polls = event.polls.all() polls = event.polls.all()
poll_ciphers = [] poll_ciphers = []
@ -305,7 +305,8 @@ def event_trustee_decrypt(request, event_id):
"polls/event_decrypt.html", "polls/event_decrypt.html",
{ {
"event": event, "event": event,
"user_email": email_key[0].user.email, "user_email": trustee.email,
"trustee_pk": trustee_pk,
"poll_ciphers": poll_ciphers "poll_ciphers": poll_ciphers
}) })
@ -318,7 +319,7 @@ def event_trustee_decrypt(request, event_id):
options_count = len(options) options_count = len(options)
for j in range(options_count): for j in range(options_count):
input_name = "" input_name = str("")
input_name = "poll-" + str(i) + "-cipher-" + str(j) input_name = "poll-" + str(i) + "-cipher-" + str(j)
part_dec = request.POST[input_name] part_dec = request.POST[input_name]
@ -326,7 +327,7 @@ def event_trustee_decrypt(request, event_id):
PartialBallotDecryption.objects.create(event=event, PartialBallotDecryption.objects.create(event=event,
poll=polls[i], poll=polls[i],
option=options[j], option=options[j],
user=email_key[0].user, user=trustee,
text=part_dec) text=part_dec)
if event.all_part_decs_received(): if event.all_part_decs_received():

View file

@ -19,6 +19,7 @@
<script src="{% static 'js/papaparse.min.js' %}" type="text/javascript"></script> <script src="{% static 'js/papaparse.min.js' %}" type="text/javascript"></script>
<script src="{% static 'js/create-event-poll.js' %}" type="text/javascript"></script> <script src="{% static 'js/create-event-poll.js' %}" type="text/javascript"></script>
<script src="{% static 'js/decrypt_event.js' %}" type="text/javascript"></script> <script src="{% static 'js/decrypt_event.js' %}" type="text/javascript"></script>
<script src="{% static 'js/event_vote.js' %}" type="text/javascript"></script>
<script src="{% static 'js/encrypt.js' %}" type="text/javascript"></script> <script src="{% static 'js/encrypt.js' %}" type="text/javascript"></script>
<script type="text/javascript" src="{% static 'js/core/rand.js' %}"></script> <script type="text/javascript" src="{% static 'js/core/rand.js' %}"></script>
@ -64,113 +65,6 @@
} }
/*
Code written with "New function" comments have
been totally or mostly re-implemented by Thomas Smith
*/
dropDownFragsNotZero = function(frags) {
var valid = false;
for(var i = 0; i < frags.length; i++) {
var frag = frags[i];
if(frag !== "0") {
valid = true;
break;
}
}
return valid;
};
//new function
demosEncrypt.encryptAndSubmit = function() {
// Drop down option selection validation
if(min_selections === 1 && max_selections === 1) {
var fragments = $('#poll-options').val().split(",");
if(!dropDownFragsNotZero(fragments)) {
alert("You have to select an option in order to vote.");
return;
}
} // TODO: Checkbox validation goes here
// Disable the enc and submit button to prevent fn from being called twice
$('#keygen-btn').prop("disabled", true);
// 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);
// Obtain the user's selection (their vote) and encrypt the fragments of the binary encoding
const selection = $('#poll-options').val();
const selectionFragments = selection.split(',');
var cipherForm = document.getElementById("cipher-form");
for(var i = 0; i < selectionFragments.length; i++) {
// Encrypt this fragment for the selection
var cipher = encrypt(params, pk, parseInt(selectionFragments[i]));
// Store C1 and C2 from the cipher in 2 arrays
var c1Bytes = [];
cipher.C1.toBytes(c1Bytes);
var c2Bytes = [];
cipher.C2.toBytes(c2Bytes);
// Inject hidden input controls into the form that represents a single ballot
var c1Input = document.createElement("input");
c1Input.setAttribute("type", "hidden");
c1Input.setAttribute("name", "cipher_c1_frag_" + i);
c1Input.setAttribute("value", c1Bytes.toString());
var c2Input = document.createElement("input");
c2Input.setAttribute("type", "hidden");
c2Input.setAttribute("name", "cipher_c2_frag_" + i);
c2Input.setAttribute("value", c2Bytes.toString());
cipherForm.appendChild(c1Input);
cipherForm.appendChild(c2Input);
}
// Inject a final input control into the form which specifies the number of fragments
// That make up an encrypted vote
var fragCountInput = document.createElement("input");
fragCountInput.setAttribute("type", "hidden");
fragCountInput.setAttribute("name", "vote_frag_count");
fragCountInput.setAttribute("value", "" + selectionFragments.length);
cipherForm.appendChild(fragCountInput);
// Submit the encrypted vote to the server
$('#cipher-form').submit();
};
function getBytes(arr) { function getBytes(arr) {
for(var i = 0; i < arr.length; i++) { for(var i = 0; i < arr.length; i++) {
arr[i] = parseInt(arr[i]); arr[i] = parseInt(arr[i]);

View file

@ -12,7 +12,7 @@
{% if not forloop.first %},{% endif %} {% if not forloop.first %},{% endif %}
{ {
title: "{{ event.title }}", title: "{{ event.title }}",
slug: "{{ event.EID }}" slug: "{{ event.EID_hr }}"
} }
{% endfor %} {% endfor %}
]; ];
@ -34,6 +34,7 @@
<!-- The DEMOS1 repository can be found at: https://github.com/mlevogiannis/demos-voting --> <!-- The DEMOS1 repository can be found at: https://github.com/mlevogiannis/demos-voting -->
<!-- TODO: look into i18n translations as this was a feature implemented in DEMOS1 to enable automatic translations --> <!-- TODO: look into i18n translations as this was a feature implemented in DEMOS1 to enable automatic translations -->
<div class="container"> <div class="container">
<div class="page-header"> <div class="page-header">
<h2>Create New Event with Polls</h2> <h2>Create New Event with Polls</h2>
</div> </div>

View file

@ -4,6 +4,13 @@
{% block sk-file-name %}{{ event.title|safe }}{% endblock %} {% block sk-file-name %}{{ event.title|safe }}{% endblock %}
{% block content %} {% block content %}
<script type="text/javascript">
// This is what we expect the SK supplied by the trustee to generate
var trustee_pk = "{{ trustee_pk }}";
var tempParams = "{{ event.EID_crypto|escapejs }}";
tempParams = JSON.parse(tempParams);
</script>
<div class="container"> <div class="container">
<h2>Trustee Event Decryption for Event '{{ event.title }}'</h2> <h2>Trustee Event Decryption for Event '{{ event.title }}'</h2>
@ -49,4 +56,24 @@
</div> </div>
</div> </div>
<!-- Information Dialog called upon request -->
<div class="modal fade" id="modalDialog" role="dialog">
<div class="modal-dialog">
<!-- Dialog content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title" style="text-align: center"><strong>Thank You</strong></h4>
</div>
<div class="modal-body">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
{% endblock %} {% endblock %}

View file

@ -3,9 +3,10 @@
{% load bootstrap3 %} {% load bootstrap3 %}
{% block app_js_vars %} {% block app_js_vars %}
var option_count = {{ object.options.count }}; const OPTION_COUNT = {{ object.options.count }};
var min_selections = {{ min_selection }}; const MIN_SELECTIONS = {{ min_selection }};
var max_selections = {{ max_selection }}; const MAX_SELECTIONS = {{ max_selection }};
var selectedCount = 0;
{% endblock %} {% endblock %}
{% block content %} {% block content %}
@ -13,6 +14,7 @@
<div class="container"> <div class="container">
{% if can_vote %} {% if can_vote %}
<!-- Hidden fields --> <!-- Hidden fields -->
{% csrf_token %}
<input id="event-param" type="text" value="{{event.EID}}" hidden/> <input id="event-param" type="text" value="{{event.EID}}" hidden/>
<input id="comb_pk" type="text" value="{{event.public_key}}" hidden/> <input id="comb_pk" type="text" value="{{event.public_key}}" hidden/>
@ -37,7 +39,15 @@
You will be shown each poll for this event one by one where you will need to make a selection for the current You will be shown each poll for this event one by one where you will need to make a selection for the current
poll before moving onto the next poll. <strong>For this specific poll</strong> you need to make a <strong> poll before moving onto the next poll. <strong>For this specific poll</strong> you need to make a <strong>
minimum</strong> of {{ min_selection }} option selection(s) and a <strong>maximum</strong> of minimum</strong> of {{ min_selection }} option selection(s) and a <strong>maximum</strong> of
{{ max_selection }}. <br/><br/>Please make your choice below. {{ max_selection }}.
{% if min_selection == 0 %}
<br/><br/>Due to the fact that you are permitted to select nothing, simply hitting submit will submit a
'blank' vote so please be aware of this. You can always re-visit this page before the event ends if you
change your mind.
{% endif %}
<br/><br/>Please make your choice below.
</span> </span>
<!-- Poll Voting Section --> <!-- Poll Voting Section -->
@ -48,18 +58,30 @@
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"><strong>Options</strong></div> <div class="panel-heading"><strong>Options</strong></div>
<div class="panel panel-body"> <div class="panel panel-body">
<select class="radio-inline select form-control" id="poll-options" name="options"> {% comment %}<select class="radio-inline select form-control" id="poll-options" name="options">
{% load custom_filters_tags %} {% load custom_filters_tags %}
<option value="{{ -1|get_ballot_value:object.options.all.count }}">Please Select...</option> <option value="{{ -1|get_ballot_value:object.options.all.count }}">Please Select...</option>
{% for option in object.options.all %} {% for option in object.options.all %}
<option value="{{forloop.counter|get_ballot_value:object.options.all.count}}">{{ option.choice_text }}</option> <option value="{{forloop.counter|get_ballot_value:object.options.all.count}}">{{ option.choice_text }}</option>
{% endfor %} {% endfor %}
</select> </select>{% endcomment %}
{% for option in object.options.all %}
<div class="checkbox">
{% load custom_filters_tags %}
<label><input type="checkbox" value="{{forloop.counter|get_ballot_value:object.options.all.count}}">{{ option.choice_text }}</label>
</div>
{% endfor %}
<hr/> <hr/>
<button id="keygen-btn" onclick="demosEncrypt.encryptAndSubmit()" class="btn btn-primary">Submit</button> <div id="ballot-gen-progress-area">
<form id="cipher-form" method="post" action="" class=""> <button id="gen-ballots-btn" class="btn btn-primary">Generate Ballots</button>
{% csrf_token %} <!-- Progress bar which is used during encryption -->
</form> <h4 id="progress-bar-description" class="hidden">Generating Ballots...</h4>
<div id="progress-bar-container" class="progress hidden">
<div id="progress-bar" class="progress-bar" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100" style="width: 0%;">
<span class="sr-only">70% Complete</span>
</div>
</div>
</div>
</div> </div>
</div> </div>
@ -78,6 +100,27 @@
</a> </a>
{% endif %} {% endif %}
</div> </div>
<!-- Information Dialog called upon request -->
<div class="modal fade" id="modalDialog" role="dialog">
<div class="modal-dialog">
<!-- Dialog content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title" style="text-align: center"><strong>Ballot</strong></h4>
</div>
<div class="modal-body">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
{% else %} <!-- for: { if can_vote %} --> {% else %} <!-- for: { if can_vote %} -->
<div class="alert alert-warning" role="alert"> <div class="alert alert-warning" role="alert">
<p>{{ cant_vote_reason }}</p> <p>{{ cant_vote_reason }}</p>

View file

@ -179,3 +179,8 @@ input[type="file"] {
.overviewPadding { .overviewPadding {
padding-left: 16px; padding-left: 16px;
} }
/* Voting page */
.big-checkbox {
width: 30px; height: 30px;
}

View file

@ -1,3 +1,59 @@
//SK checking algorithm - If PK and SK matches, it returns True; otherwise, it returns false.
// Written by Bingsheng Zhang
function skCheck(ctx, params, SK, PK) {
var D = ctx.PAIR.G1mul(params.g1, SK);
return D.equals(PK)
}
function validateSKFromString(SKStr) {
// Re-create the SK from the string byte definition
let ctx = new CTX("BN254CX");
let skBytes = SKStr.split(",");
let sk = new ctx.BIG.fromBytes(skBytes);
// Re-create the params
let n = new ctx.BIG();
let g1 = new ctx.ECP();
let g2 = new ctx.ECP2();
n.copy(tempParams.n);
g1.copy(tempParams.g1);
g2.copy(tempParams.g2);
let params = {
n:n,
g1:g1,
g2:g2
};
// Re-create the trustee PK from the string byte definition
let pkBytes = trustee_pk.split(',').map(function(byteStr) {
return parseInt(byteStr)
});
let pk = new ctx.ECP.fromBytes(pkBytes);
// Check that the SK supplies generates the PK we know about
return skCheck(ctx, params, sk, pk);
}
function showDialog(titleTxt, bodyTxt) {
var modalDialog = $('#modalDialog');
var title = modalDialog.find('.modal-title');
var body = modalDialog.find('.modal-body');
title.text(titleTxt);
var bodyText = bodyTxt;
var p = document.createElement("p");
p.innerHTML = bodyText;
body.empty();
body.append( p );
modalDialog.modal('show');
}
function processFileSKChange(event) { function processFileSKChange(event) {
var files = event.target.files; var files = event.target.files;
@ -6,7 +62,23 @@ function processFileSKChange(event) {
var reader = new FileReader(); var reader = new FileReader();
reader.onload = function(e) { reader.onload = function(e) {
$('input#secret-key').val(reader.result); var SKStr = reader.result;
// Check that the SK string is not blank
if(SKStr === '') {
// Show a dialog informing the user that they've uploaded a blank file
showDialog('Error', 'The file you have uploaded is blank.');
}
const valid = validateSKFromString(SKStr);
if(valid) {
$('input#secret-key').val(SKStr);
} else {
// Show a dialog informing the user that they've supplied an invalid SK
showDialog('Error',
'The secret key you have supplied is invalid and doesn\'t match with the recorded public key.');
}
}; };
reader.readAsText(files[0]); reader.readAsText(files[0]);

273
static/js/event_vote.js Normal file
View file

@ -0,0 +1,273 @@
// 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 dropDownFragsNotZero(frags) {
var valid = false;
for(var i = 0; i < frags.length; i++) {
var frag = frags[i];
if(frag !== "0") {
valid = true;
break;
}
}
return valid;
}
function isVotingInputValid() {
var valid = true;
// First establish if the user's selection count is valid
if(!(selectedCount >= MIN_SELECTIONS && selectedCount <= MAX_SELECTIONS)) {
valid = false;
}
// This will highlight when people haven't selected enough options
if(!valid) {
var modalDialog = $('#modalDialog');
var title = modalDialog.find('.modal-title');
var body = modalDialog.find('.modal-body');
var errText = "You've only selected " + selectedCount
+ " option(s). The minimum number you need to select is " + MIN_SELECTIONS
+ " and the maximum is " + MAX_SELECTIONS + ". Please go back and correct this.";
title.text('Voting Error');
var p = document.createElement("p");
p.innerHTML = errText;
body.empty();
body.append( p );
modalDialog.modal('show');
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 progress = 0;
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 to an array
if(checkbox.prop('checked')) {
unencryptedVotes.push(checkbox.val());
}
// For whatever hasn't been selected, push a blank vote to the array
else {
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
});
});
var ballot = {
encryptedVotes: encryptedVotes
};
return ballot;
}
$('#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, 50);
}
});
function voteSuccessfullyReceived() {
var modalDialog = $('#modalDialog');
var title = modalDialog.find('.modal-title');
var body = modalDialog.find('.modal-body');
title.text('Vote Successfully Received');
var bodyText = "Thank you for voting!";
var p = document.createElement("p");
p.innerHTML = bodyText;
body.empty();
body.append( p );
modalDialog.modal('show');
}
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(ballot) {
$.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({ ballot: ballot}),
success : function(){
voteSuccessfullyReceived();
}
});
}
function generateBallotB(ballotA) {
var ballotB = generateBallot();
progressBar.setAttribute("style", "width: 100%;");
var ballots = {
A : ballotA,
B : ballotB
};
// TODO: Implement ballot choice UI and QR func here. At the moment the code automatically
// TODO: submits the first ballot (as if the user selected it) to the server but this needs updating.
// 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 ballot choices.
// This delay allows the execution thread to update the above CSS on the progress bar
var selectedBallot = ballots.A;
setTimeout(function () {
sendBallotToServer(selectedBallot);
}, 50);
}
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);
}, 125);
}