Merge pull request #10 from vincentmdealmeida/BinaryEncoding

Implemented full end-to-end encryption and decryption of an event usi…
This commit is contained in:
vincentmdealmeida 2018-07-07 09:54:59 +01:00 committed by GitHub
commit 571cd723bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 809 additions and 446 deletions

View File

@ -1,30 +1,33 @@
/*
Code by Thomas Smith
Code by Bingsheng Zhang, Thomas Smith, Vincent de Almeida
Dependencies can be found in 'package.json' and installed using 'npm install'
*/
var port = 8080;
var express = require('express');
var Buffer = require('buffer').Buffer;
var CTX = require('milagro-crypto-js')
var app = express();
/*
var cors = require('cors')
app.use(cors());
*/
var CTX = require('milagro-crypto-js');
var express = require('express');
var bodyParser = require("body-parser");
var app = express();
// Express server configuration
app.use(express.static('test'));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
//default test
app.get('/', function(request, response){
var data = {
message: 'hello world',
value: 5
}
};
//response.send('Hey there'+request.ip);
@ -40,13 +43,13 @@ app.get('/param', function(request, response){
console.log('Generated Param:' + param);
response.json(param);
})
});
//combine public keys and return the full combined one - JSON Version
app.get('/combpk', function(request, response){
console.log('\nEndpoint /combpk called');
var partials = request.query['PK']
var partials = request.query['PK'];
var parsed = [];
@ -57,45 +60,40 @@ app.get('/combpk', function(request, response){
parsed.push(JSON.parse(partials[i]));
}
var PK = combine(parsed);
var PK = combine_pks(parsed);
response.json(PK);
})
});
//byte array version
app.get('/cmpkstring', function(request, response){
app.post('/cmpkstring', function(request, response){
console.log('\nEndpoint /cmpkstring called');
var ctx = new CTX("BN254CX");
var partials = request.query['PK']
//if there is only one key, partials will be an array of the individual bytes
//if more than one, it will be an array of arrays
//we need to factor for this in code
var noOfKeys = request.query['number'];
var partials = request.body.PKs;
var parsed = [];
if(noOfKeys == partials.length)//if we're submitting more than one key
if(partials.length > 1)//if we're submitting more than one key
{
console.log('Combining' + noOfKeys + " keys...");
console.log('Combining ' + partials.length + " public keys into one...");
for (var i = partials.length - 1; i >= 0; i--) {
console.log('PK' +i+ ': '+partials[i]);
var bytes = Buffer.from(partials[i].split(','), 'hex');
console.log(bytes)
var pk = new ctx.ECP.fromBytes(bytes);
parsed.push(pk);
console.log('PK' + i + ': ' + partials[i]);
var bytes = Buffer.from(partials[i].split(','), 'hex');
var pk = new ctx.ECP.fromBytes(bytes);
parsed.push(pk);
}
}
else if(noOfKeys == 1)
else if(partials.length === 1)
{
console.log("Combining just one key");
var bytes = Buffer.from(partials.split(','), 'hex');
console.log(bytes);
console.log("Combining just one public key...");
var bytes = Buffer.from(partials[0].split(','), 'hex');
var pk = new ctx.ECP.fromBytes(bytes);
parsed.push(pk);
}
response.json(combine(parsed));
})
response.json(combine_pks(parsed));
});
//addition function on homomorphically encrypted variables
@ -155,20 +153,19 @@ app.get('/addec', function(request, response){
response.json(add(parsed));
})
});
//tally partially decrypted ciphertexts
app.get('/tally', function(request, response){
console.log("called tally");
console.log("\nEndpoint /tally called");
var amount = request.query['number'];//number of decryptions taking in
var paramString = request.query['param'];//event group parameter in JSON
var partialsStrings = request.query['decs'];//array of partial decryption(s) in bytes
var ciphertextString = request.query['cipher'];//ciphertext being decrypted in JSON
//re-build parameters
var tempParams = JSON.parse(paramString);
var tempParams = JSON.parse(JSON.parse(paramString).crypto);
var ctx = new CTX("BN254CX"); //new context we can use
var n = new ctx.BIG();
var g1 = new ctx.ECP();
@ -183,47 +180,117 @@ app.get('/tally', function(request, response){
n:n,
g1:g1,
g2:g2
}
};
//re-build partial decryptions
var partials = []
var partials = [];
if(amount == partialsStrings.length)
{
console.log(amount + " partial decryptions");
for(var i = 0; i < partialsStrings.length; i++)
{
var bytes = Buffer.from(partialsStrings[i].split(','), 'hex');
var dec = {
D:new ctx.ECP.fromBytes(bytes)
}
};
partials.push(dec);
}
}
else if(amount == 1)
{
console.log("Only one partial decryption received")
console.log(paramString)
console.log("\nOnly one partial decryption received\n");
console.log(JSON.parse(paramString).crypto + "\n");
var bytes = Buffer.from(partialsStrings.split(','), 'hex');
var dec = {
D:new ctx.ECP.fromBytes(bytes)
}
D : new ctx.ECP.fromBytes(bytes)
};
partials.push(dec);
}
//re-build combined ciphertext
var tempCipher = JSON.parse(ciphertextString);
cipher = {
var cipher = {
C1: new ctx.ECP(),
C2: new ctx.ECP()
}
};
cipher.C1.copy(tempCipher.C1);
cipher.C2.copy(tempCipher.C2);
response.json(tally(params, partials, cipher))
})
});
app.post('/comb_sks', function(request, response){
console.log("\nEndpoint /comb_sks called");
const SKsAsStrings = request.body.SKs;
// Parse and combine the secret keys
var ctx = new CTX("BN254CX");
var parsedSKs = [];
for(var i = 0; i < SKsAsStrings.length; i++) {
var skBytes = SKsAsStrings[i].split(",");
parsedSKs.push(new ctx.BIG.fromBytes(skBytes));
}
console.log("Combining " + parsedSKs.length + " SKs...");
var SKBytes = [];
combine_sks(parsedSKs).SK.toBytes(SKBytes);
response.send(SKBytes.toString());
});
app.post('/get_tally', function(request, response){
const COUNT = request.body.count;
const TEMP_PARAMS = JSON.parse(JSON.parse(request.body.param).crypto);
const C1s = request.body.ciphers.c1s;
const C2s = request.body.ciphers.c2s;
const SK = request.body.sk;
console.log("\nFrom /get_tally - C1 array length (num of voters for the opt): " + C1s.length);
//re-build parameters
var ctx = new CTX("BN254CX"); //new context we can use
var n = new ctx.BIG();
var g1 = new ctx.ECP();
var g2 = new ctx.ECP2();
n.copy(TEMP_PARAMS.n);
g1.copy(TEMP_PARAMS.g1);
g2.copy(TEMP_PARAMS.g2);
var params = {
n:n,
g1:g1,
g2:g2
};
//rebuild our secret key
var skBytes = SK.split(",");
var sk = new ctx.BIG.fromBytes(skBytes);
var tally = 0;
for(var i = 0; i < COUNT; i++) {
var c1Bytes = Buffer.from(C1s[i].split(','), 'hex');
var newC1 = new ctx.ECP.fromBytes(c1Bytes);
var c2Bytes = Buffer.from(C2s[i].split(','), 'hex');
var newC2 = new ctx.ECP.fromBytes(c2Bytes);
var cipher = {C1: newC1, C2: newC2};
tally += decrypt(params, sk, cipher).M;
}
console.log("Tally: " + tally + "\n");
response.send("" + tally);
});
var server = app.listen(port, function(){
var host = server.address().address;
@ -277,7 +344,7 @@ gpGen = function(){
g1:P,
g2:Q
}
}
};
//creates ElGamal public and secret key
@ -304,12 +371,12 @@ keyGen=function(params){
PK:pk,
SK:sk
}
}
};
//combine multiple public key together
//the input is an array of PKs
combine=function(PKs){
combine_pks=function(PKs){
var ctx = new CTX("BN254CX");
var pk=new ctx.ECP();
//copy the first pk
@ -319,11 +386,25 @@ combine=function(PKs){
pk.add(PKs[i]);
}
return{
PK:pk
return {
PK : pk
}
}
};
// Written by Vincent de Almeida: Combines multiple secret keys together
// The SKs in the SKs array should already have been initialised using 'new ctx.BIG.fromBytes()'
combine_sks=function(SKs) {
// 'add' the rest of the sks to the first
var sk = SKs[0];
for(var i = 1; i < SKs.length; i++) {
sk.add(SKs[i]);
}
return {
SK: sk
}
};
//ElGamal encryption
encrypt=function(params,PK, m){
@ -356,7 +437,7 @@ encrypt=function(params,PK, m){
C1:C1,
C2:C2
}
}
};
//add ciphertexts
@ -380,7 +461,7 @@ add=function(Ciphers){
C1:s1,
C2:s2
}
}
};
//ElGamal decryption
@ -410,9 +491,7 @@ decrypt=function(params,SK, C){
return{
M: "Error"
}
}
};
//ElGamal partial decryption
@ -424,8 +503,7 @@ partDec=function(SK, C){
return{
D: D
}
}
};
@ -446,24 +524,24 @@ tally=function(params,Ds, C){
gM.copy(C.C2);
gM.sub(D);
//search for message by brute force
//search for message by brute force
var B;
for (j = 0; j < 1000; j++) {
for (var j = 0; j < 1000; j++) {
//use D as temp var
B = new ctx.BIG(j);
D = ctx.PAIR.G1mul(params.g1,B);
if (D.equals(gM))
return{
M:j
M: j
}
};
}
return{
M: "Error"
}
}
};

View File

@ -14,9 +14,10 @@
"url": "https://github.com/vincentmdealmeida/DEMOS2"
},
"keywords": [],
"author": "Bingsheng Zang, Thomas Smith",
"author": "Bingsheng Zang, Thomas Smith, Vincent de Almeida",
"license": "ISC",
"dependencies": {
"body-parser": "^1.18.3",
"express": "^4.16.3",
"milagro-crypto-js": "git+https://github.com/milagro-crypto/milagro-crypto-js.git"
}

View File

@ -1,6 +1,3 @@
import os
import shlex
import subprocess
import json
import urllib2
@ -10,26 +7,40 @@ All functions in this file have been re-implemenented by Thomas Smith
File then updated by Vincent de Almeida. Changes include:
-Update filename to 'crypto_rpc' to reflect the RPC nature of the methods
-Modified RPC calls that send data to POST requests to avoid large query URLs
'''
def send_post_req(url, data):
data = json.dumps(data)
# Create a request specifying the Content-Type
req = urllib2.Request(url, data, {'Content-Type': 'application/json'})
f = urllib2.urlopen(req)
response = f.read()
f.close()
return response
def param():
url = 'http://localhost:8080/param' # RPC URL
url = 'http://localhost:8080/param'
jsondict = json.load(urllib2.urlopen(url))
return json.dumps(jsondict)
def combpk(amount, pks):
url = 'http://localhost:8080/cmpkstring' # RPC URL
querystring = '?number='+str(amount)
for pk in pks:
querystring += '&PK='+pk
print(url+querystring)
jsondict = json.load(urllib2.urlopen(url+querystring))
print(json.dumps(jsondict))
return json.dumps(jsondict)
def combpk(pks):
url = 'http://localhost:8080/cmpkstring'
data = {}
data['PKs'] = pks
return send_post_req(url, data)
def addec(amount, ciphers):
url = 'http://localhost:8080/addec' # RPC URL
url = 'http://localhost:8080/addec'
querystring = '?number='+str(amount)
c1s = ciphers['c1s']
c2s = ciphers['c2s']
@ -42,23 +53,44 @@ def addec(amount, ciphers):
print(json.dumps(jsondict))
return json.dumps(jsondict)
def tally(amount, param, decs, cipher):
url = 'http://localhost:8080/tally' # RPC URL
querystring = '?number='+str(amount)
querystring += '&param='+urllib2.quote(str(param))
testquerystring = '?number='+str(amount)
testquerystring += '&param='+str(param)
# Deprecated functionality and has been superseded by get_tally
def tally(amount, group_param, decs, cipher):
url = 'http://localhost:8080/tally'
querystring = '?number='+str(amount)
querystring += '&param='+urllib2.quote(str(group_param))
for i, value in enumerate(decs):
querystring += "&decs="+str(value)
testquerystring += "&decs="+str(value)
querystring += '&cipher=' + urllib2.quote(str(cipher))
testquerystring += '&cipher=' + str(cipher)
print(url+querystring)
print(url+testquerystring)
jsondict = json.load(urllib2.urlopen(url+querystring))
print('tally: ' + str(jsondict['M']))
return str(jsondict['M'])
return str(jsondict['M'])
def combine_sks(sks):
url = 'http://localhost:8080/comb_sks'
# Construct POST data
data = {}
data['SKs'] = sks
# Return the new combined SK
return send_post_req(url, data)
def get_tally(count, ciphers, sk, group_param):
url = 'http://localhost:8080/get_tally'
# Construct POST data
data = {}
data['count'] = count
data['ciphers'] = ciphers
data['sk'] = sk
data['param'] = group_param
# Return the tally of votes for the option
return send_post_req(url, data)

View File

@ -28,6 +28,7 @@ class Event(models.Model):
start_time = models.DateTimeField()
end_time = models.DateTimeField()
prepared = models.BooleanField(default=False)
ended = models.BooleanField(default=False)
public_key = models.CharField(null=True, blank=False, max_length=1024)
title = models.CharField(max_length=1024)
EID = models.CharField(max_length=2048, blank=True)
@ -35,6 +36,7 @@ class Event(models.Model):
c_email = models.CharField(max_length=512, blank=True)
trustees = models.CharField(max_length=4096)
# Custom helper methods
def EID_hr(self):
EID_json = json.loads(self.EID)
return EID_json['hr']
@ -68,15 +70,36 @@ class Event(models.Model):
# future event
present = timezone.now()
if present >= self.start_time and present <= self.end_time:
status_str = "Active"
elif present > self.end_time:
status_str = "Expired"
elif present < self.start_time:
status_str = "Future"
if self.ended is False:
if present < self.start_time and self.public_key is None:
status_str = "Future"
elif present < self.start_time and self.public_key is not None:
status_str = "Prepared"
elif present >= self.start_time and present <= self.end_time and self.public_key is not None:
status_str = "Active"
elif present > self.end_time and self.public_key is not None:
status_str = "Expired"
else:
if self.event_sk.all().count() == 1:
status_str = "Decrypted"
elif self.event_sk.all().count() == 0:
status_str = "Ended"
return status_str
'''
The result applies to all polls for an event so True will only be returned when votes have
been received for every poll.
'''
def has_received_votes(self):
received_votes = True
for poll in self.polls.all():
if Ballot.objects.filter(poll=poll, cast=True).count() == 0:
received_votes = False
return received_votes
def __str__(self):
return self.title
@ -84,12 +107,12 @@ class Event(models.Model):
class TrusteeKey(models.Model):
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="trustee_keys")
user = models.ForeignKey(EmailUser, on_delete=models.CASCADE, related_name="trustee_keys")
key = models.CharField(max_length=1024, unique=True) # ideally composite key here, but django doesn't really support yet
key = models.CharField(max_length=255, unique=True)
class AccessKey(models.Model):
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="keys")
user = models.ForeignKey(EmailUser, on_delete=models.CASCADE, related_name="keys")
key = models.CharField(max_length=1024, unique=True) # ideally composite key here, but django doesn't really support yet
key = models.CharField(max_length=255, unique=True)
#total = models.IntegerField(blank=True, null=True, default=0)
@ -115,20 +138,6 @@ class Poll(models.Model):
def __str__(self):
return self.question_text
class Decryption(models.Model):
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="decryptions")
poll = models.ForeignKey(Poll, on_delete=models.CASCADE, related_name="decryptions")
user = models.ForeignKey(EmailUser, on_delete=models.CASCADE, related_name="decryptions")
text = models.CharField(max_length=1024)
#some modification to this class
class Ballot(models.Model):
voter = models.ForeignKey(EmailUser, on_delete=models.CASCADE, related_name="ballots")
poll = models.ForeignKey(Poll, on_delete=models.CASCADE, related_name="ballots")
cipher_text_c1 = models.CharField(max_length=4096)#the encryption system uses two byte strings
cipher_text_c2 = models.CharField(max_length=4096)
cast = models.BooleanField(default=False)
class PollOption(models.Model):
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
@ -138,6 +147,35 @@ class PollOption(models.Model):
def __str__(self):
return self.choice_text
class Decryption(models.Model):
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="decryptions")
poll = models.ForeignKey(Poll, on_delete=models.CASCADE, related_name="decryptions")
user = models.ForeignKey(EmailUser, on_delete=models.CASCADE, related_name="decryptions")
text = models.CharField(max_length=1024)
class TrusteeSK(models.Model):
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="trustee_sk")
trustee = models.ForeignKey(EmailUser, on_delete=models.CASCADE, related_name="trustee_sk")
key = models.CharField(max_length=1024)
class EventSK(models.Model):
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="event_sk")
key = models.CharField(max_length=1024)
class Ballot(models.Model):
voter = models.ForeignKey(EmailUser, on_delete=models.CASCADE, related_name="ballots")
poll = models.ForeignKey(Poll, on_delete=models.CASCADE, related_name="ballots")
cast = models.BooleanField(default=False)
# Implements the new binary encoding scheme
class EncryptedVote(models.Model):
ballot = models.ForeignKey(Ballot, on_delete=models.CASCADE, related_name="encrypted_vote")
class VoteFragment(models.Model):
encrypted_vote = models.ForeignKey(EncryptedVote, on_delete=models.CASCADE, related_name="fragment")
cipher_text_c1 = models.CharField(max_length=4096)
cipher_text_c2 = models.CharField(max_length=4096)
class Organiser(models.Model):
index = models.IntegerField(default=0)
email = models.CharField(max_length=100, blank=False, null=False)

View File

@ -5,14 +5,11 @@ import json
from os import urandom
from celery import task
from django.core.exceptions import ValidationError
from django.core.validators import EmailValidator
from django.core.mail import send_mail
from django.conf import settings
from allauthdemo.polls.models import AccessKey
from allauthdemo.polls.models import AccessKey, Ballot, Decryption, TrusteeSK, EventSK
from .crypto_rpc import param, combpk, addec, tally
from .crypto_rpc import param, combpk, addec, tally, get_tally, combine_sks
'''
Goal: This py file defines celery tasks that can be initiated
@ -25,25 +22,43 @@ from .crypto_rpc import param, combpk, addec, tally
# Will store the result of the initial cal to param() from .cpp_calls
group_param = None
def is_valid_email(email):
try:
valid_email = EmailValidator(whitelist=None)
valid_email(email)
return True
except ValidationError:
return False
@task()
def create_ballots(poll):
for voter in poll.event.voters.all():
ballot = poll.ballots.create(voter=voter, poll=poll)
'''
Will generate a key for accessing either the event preparation page or the voting page
Helper functions
gen_access_key - Will generate an a key for accessing either the event preparation page, voting page and decryption page
'''
def gen_access_key():
return base64.urlsafe_b64encode(urandom(16)).decode('utf-8')
def email_trustees_dec(event):
email_subject = "Event Ballot Decryption for '" + event.title + "'"
# Plain text email - this could be replaced for a HTML-based email in the future
email_body = "Please visit the following URL to submit your trustee secret key to begin event decryption:\n\n"
url_base = "http://" + settings.DOMAIN + "/event/" + str(event.pk) + "/decrypt/?key="
email_body = email_body + url_base
for trustee in event.users_trustees.all():
# Generate a key and create an AccessKey object
key = gen_access_key()
AccessKey.objects.create(user=trustee, event=event, key=key)
trustee.send_email(email_subject, email_body + key)
@task()
def create_ballots(event):
voters = event.voters.all()
for poll in event.polls.all():
for voter in voters:
ballot = poll.ballots.create(voter=voter, poll=poll)
@task()
def create_ballots_for_poll(poll):
for voter in poll.event.voters.all():
ballot = poll.ballots.create(voter=voter, poll=poll)
'''
Emails an event preparation URL containing an access key for all of the trustees for an event
'''
@ -64,19 +79,31 @@ def email_trustees_prep(trustees, event):
trustee.send_email(email_subject, email_body + key)
'''
Emails the access keys for all of the voters for an event
Emails a URL containing an access key for all of the voters for an event
'''
@task()
def email_voters_a_key(voters, event):
def email_voters_vote_url(voters, event):
email_subject = "Voting Access for Event '" + event.title + "'"
email_body = 'Key: '
# Plain text email - this could be replaced for a HTML-based email in the future
email_body_base = "Please visit the following URL in order to vote on the event '" + event.title + "':\n\n"
url_base = "http://" + settings.DOMAIN + "/event/" + str(event.pk) + "/poll/1/vote/?key="
email_body_base = email_body_base + url_base
duration_info = "\n\nYou can vote between the following dates and times:\n"
duration_info = duration_info + "Start: " + event.start_time_formatted_utc() + "\n"
duration_info = duration_info + "End: " + event.end_time_formatted_utc()
for voter in voters:
# Generate a key and create an AccessKey object
key = gen_access_key()
AccessKey.objects.create(user=voter, event=event, key=key)
voter.send_email(email_subject, email_body + key)
# Update the email body to incl the access key as well as the duration information
email_body = str(email_body_base + key)
email_body = email_body + duration_info
voter.send_email(email_subject, email_body)
'''
Updates the EID of an event to contain 2 event IDs: a human readable one (hr) and a crypto one (GP from param())
@ -93,6 +120,83 @@ def update_EID(event):
event.EID = json.dumps(EID)
event.save()
@task()
def event_ended(event):
# Email all trustees to request their secret keys
email_trustees_dec(event)
@task()
def gen_event_sk_and_dec(event):
trustee_sks = TrusteeSK.objects.filter(event=event)
t_sks_count = len(trustee_sks)
# Combine SKs if there's more than one
event_sk = None
if t_sks_count == 1:
event_sk = trustee_sks.get().key
else:
t_sks_str_list = list()
for t_sk in trustee_sks:
t_sks_str_list.append(t_sk.key)
event_sk = combine_sks(t_sks_str_list)
EventSK.objects.create(event=event, key=event_sk)
# With the event sk created, we can decrypt the event
decrypt_and_tally(event)
@task()
def decrypt_and_tally(event):
polls = event.polls.all()
sk = EventSK.objects.filter(event=event).get().key
for i in range(len(polls)):
poll = polls[i]
result = str("")
result += "{\"name\": \"" + poll.question_text + "\","
# get num of opts and ballots
options = poll.options.all()
opt_count = len(options)
ballots = Ballot.objects.filter(poll=poll, cast=True)
result += "\"options\": ["
for j in range(opt_count):
# Collect all fragments for this opt
frags_c1 = list()
frags_c2 = list()
for ballot in ballots:
enc_vote = ballot.encrypted_vote.get()
if enc_vote is not None:
fragments = enc_vote.fragment.all()
frags_c1.append(fragments[j].cipher_text_c1)
frags_c2.append(fragments[j].cipher_text_c2)
ciphers = {
'c1s': frags_c1,
'c2s': frags_c2
}
count = len(frags_c1)
votes = get_tally(count, ciphers, sk, event.EID)
result += "{\"option\": \"" + str(options[j].choice_text) + "\", \"votes\": " + str(votes) + "}"
if j != (opt_count-1):
result += ","
result += "]}"
if i != (len(polls) - 1):
result += ","
poll.enc = result
poll.save()
@task()
def tally_results(event):
for poll in event.polls.all():
@ -101,39 +205,42 @@ def tally_results(event):
decs.append(dec.text)
amount = len(decs)
result = tally(amount, event.EID, decs, poll.enc)
send_mail(
'Your Results:',
poll.question_text + ": " + result,
'from@example.com',
["fake@fake.com"],
fail_silently=False,
)
# TODO: Email organisers using email_user method?
print(poll.question_text + ": " + result)
@task()
def generate_combpk(event):
pks = list()
for tkey in event.trustee_keys.all():
pks.append(str(tkey.key))
amount = len(pks)
event.public_key = combpk(amount, pks)
event.public_key = combpk(pks)
event.prepared = True
event.save()
@task
def generate_enc(poll):
c1s = list()#c1 components of ciphertexts
c2s = list()#c1 components of ciphertexts
# c1 and c2 components of ciphertexts
c1s = list()
c2s = list()
for ballot in poll.ballots.all():
if (ballot.cast):
if ballot.cast:
c1s.append(str(ballot.cipher_text_c1))
c2s.append(str(ballot.cipher_text_c2))
ciphers = {
'c1s':c1s,
'c2s':c2s
'c1s': c1s,
'c2s': c2s
}
amount = len(c1s)
poll.enc = addec(amount, ciphers)
count = len(c1s)
poll.enc = addec(count, ciphers)
poll.save()

View File

@ -5,5 +5,17 @@ register = template.Library()
#get a value for additively homomorphic encryption ballots
#we can't do maths in the template normally so a filter is a way around it
@register.filter
def get_ballot_value(value):
return pow(10, value-1)
def get_ballot_value(option_no, options_count):
ballot_value = ""
for i in range(options_count):
if (i+1) == option_no:
ballot_value = ballot_value + "1"
else:
ballot_value = ballot_value + "0"
if not i == (options_count-1):
ballot_value = ballot_value + ","
return ballot_value

View File

@ -1,37 +1,24 @@
from django.conf.urls import url
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth.decorators import login_required
from . import views
app_name = 'polls'
#urlpatterns = [
# url(r'^$', views.index, name='index'),
# ex: /polls/5/
# url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
# ex: /polls/5/results/
# url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
# ex: /polls/5/vote/
# url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
#]
urlpatterns = [
url(r'^vote/(?P<poll_id>[0-9]+)/$', views.test_poll_vote, name='vote-poll'),
url(r'^(?P<pk>[0-9]+)/$', views.EventDetailView.as_view(), name='view-event'),
url(r'^(?P<pk>[0-9]+)/polls$', views.EventDetailPollsView.as_view(), name='event-polls'),
url(r'^(?P<pk>[0-9]+)/organisers$', views.EventDetailOrganisersView.as_view(), name='event-organisers'),
url(r'^$', views.EventListView.as_view(), name='index'),
url(r'^$', login_required(views.EventListView.as_view()), name='index'),
url(r'^create/$', login_required(views.create_event), name='create-event'),
url(r'^(?P<event_id>[0-9]+)/decrypt/$', login_required(views.event_trustee_decrypt), name='decrypt-event'),
url(r'^(?P<event_id>[0-9]+)/prepare/$', login_required(views.event_trustee_setup), name='prepare-event'),
url(r'^(?P<event_id>[0-9]+)/encrypt/$', login_required(views.event_addec), name='enc-event'),
url(r'^(?P<pk>[0-9]+)/launch/$', views.EventDetailLaunchView.as_view(), name='launch-event'),
url(r'^edit/(?P<event_id>[0-9]+)/$', login_required(views.edit_event), name='edit-event'),
url(r'^delete/(?P<event_id>[0-9]+)/$', login_required(views.del_event), name='del-event'),
url(r'^(?P<pk>[0-9]+)/$', login_required(views.EventDetailView.as_view()), name='view-event'),
url(r'^(?P<pk>[0-9]+)/polls/$', login_required(views.EventDetailPollsView.as_view()), name='event-polls'),
url(r'^(?P<pk>[0-9]+)/entities/$', login_required(views.EventDetailEntitiesView.as_view()), name='event-entities'),
url(r'^(?P<pk>[0-9]+)/advanced/$', login_required(views.EventDetailAdvancedView.as_view()), name='event-advanced'),
url(r'^(?P<event_id>[0-9]+)/end/$', login_required(views.event_end), name='end-event'),
url(r'^(?P<event_id>[0-9]+)/results/$', login_required(views.results), name='event-results'),
url(r'^(?P<event_id>[0-9]+)/edit/$', login_required(views.edit_event), name='edit-event'),
url(r'^(?P<event_id>[0-9]+)/delete/$', login_required(views.del_event), name='del-event'),
url(r'^(?P<event_id>[0-9]+)/decrypt/$', views.event_trustee_decrypt, name='decrypt-event'),
url(r'^(?P<event_id>[0-9]+)/prepare/$', views.event_trustee_setup, name='prepare-event'),
url(r'^(?P<event_id>[0-9]+)/poll/(?P<poll_num>[0-9]+)/vote/$', views.event_vote, name='event-vote'),
url(r'^(?P<event_id>[0-9]+)/create/poll/$', login_required(views.manage_questions), name='create-poll'),
url(r'^(?P<event_id>[0-9]+)/poll/(?P<poll_num>[0-9]+)/$', login_required(views.view_poll), name='view-poll'),
url(r'^(?P<event_id>[0-9]+)/poll/(?P<poll_num>[0-9]+)/edit$', login_required(views.edit_poll), name='edit-poll'),
#url(r'^(?P<pk>[0-9]+)/$', login_required(views.DetailView.as_view()), name='detail'),
#url(r'^(?P<pk>[0-9]+)/results/$', login_required(views.ResultsView.as_view()), name='results'),
#url(r'^(?P<question_id>[0-9]+)/vote/$', login_required(views.vote), name='vote'),
url(r'^(?P<event_id>[0-9]+)/poll/(?P<poll_num>[0-9]+)/edit$', login_required(views.edit_poll), name='edit-poll')
]

View File

@ -539,12 +539,9 @@ class EventModelAdaptor:
# Extract the list of trustees
trustees_list = self.form_data.pop('trustee-email-input')
for trustee in trustees_list:
if trustee != '':
if EmailUser.objects.filter(email=trustee).exists():
self.trustees.append(EmailUser.objects.filter(email=trustee).get())
else:
self.trustees.append(EmailUser(email=trustee))
for trustee_email in trustees_list:
if trustee_email != '':
self.trustees.append(trustee_email)
# Extract the email list of voters
voters_csv_string = self.form_data.pop('voters-list-input')[0].replace(' ', '')
@ -552,17 +549,21 @@ class EventModelAdaptor:
for voter_email in voters_email_list:
if voter_email != '':
if EmailUser.objects.filter(email=voter_email).exists():
self.voters.append(EmailUser.objects.filter(email=voter_email).get())
else:
self.voters.append(EmailUser(email=voter_email))
self.voters.append(voter_email)
# Create the Event model object - this does not persist it to the DB
creator = ""
if self.user.first_name is not None:
creator += self.user.first_name + " "
if self.user.last_name is not None:
creator += self.user.last_name
self.event = Event(start_time=self.starts_at,
end_time=self.ends_at,
title=self.event_name,
EID=self.identifier,
creator=self.user.first_name + ' ' + self.user.last_name,
creator=creator,
c_email=self.user.email,
trustees=voters_csv_string)
@ -629,20 +630,21 @@ class EventModelAdaptor:
# so it can just be added
self.event.users_organisers = self.organisers
# Add the list of trustees to the event, making sure they're instantiated
# Add the list of trustees to the event
db_trustees = list()
for trustee in self.trustees:
if not EmailUser.objects.filter(email=trustee.email).exists():
trustee.save()
user, created = EmailUser.objects.get_or_create(email=trustee)
db_trustees.append(user)
self.event.users_trustees = self.trustees
self.event.users_trustees = db_trustees
# Add the list of voters to the event, making sure they're instantiated
# Additionally, generating the AccessKey for voters
# Add the list of voters to the event
db_voters = list()
for voter in self.voters:
if not EmailUser.objects.filter(email=voter.email).exists():
voter.save()
user, created = EmailUser.objects.get_or_create(email=voter)
db_voters.append(user)
self.event.voters = self.voters
self.event.voters = db_voters
# Extract all the poll data for the event and associated poll option data
# This can only be done at this point as the event has been persisted

View File

@ -2,25 +2,23 @@ import urllib
import urllib2
import json
from io import StringIO
from django.contrib import messages
from django.http import HttpResponseRedirect, HttpResponse, Http404
from django.http.response import HttpResponseNotAllowed
from django.core.urlresolvers import reverse
from django.shortcuts import get_object_or_404, render, render_to_response
from django.utils import timezone
from django.shortcuts import get_object_or_404, render
from django.views import generic
from django.conf import settings
from django.core import serializers
from .forms import EventForm, PollForm, OptionFormset, QuestionFormset, OrganiserFormSet, TrusteeFormSet, VoteForm, EventSetupForm, EventEditForm, DecryptionFormset, DecryptionFormSetHelper
from .models import Event, Poll, PollOption, EmailUser, Ballot, TrusteeKey, Decryption
from .forms import PollForm, OptionFormset, VoteForm, EventSetupForm, EventEditForm
from .models import Event, Poll, Ballot, EncryptedVote, TrusteeKey, TrusteeSK
from allauthdemo.auth.models import DemoUser
from .tasks import email_trustees_prep, update_EID, generate_combpk, generate_enc, tally_results
from .tasks import email_trustees_prep, update_EID, generate_combpk, event_ended, create_ballots, create_ballots_for_poll, email_voters_vote_url, gen_event_sk_and_dec
from .utils.EventModelAdaptor import EventModelAdaptor
class EventListView(generic.ListView):
model = Event
@ -30,6 +28,7 @@ class EventListView(generic.ListView):
#context['now'] = timezone.now()
return context
class EventDetailView(generic.DetailView):
template_name="polls/event_detail_details.html"
model = Event
@ -37,38 +36,32 @@ class EventDetailView(generic.DetailView):
def get_context_data(self, **kwargs):
context = super(EventDetailView, self).get_context_data(**kwargs)
context['is_organiser'] = ((not self.request.user.is_anonymous()) and (self.object.users_organisers.filter(email=self.request.user.email).exists()))
context['decrypted'] = self.object.status() == "Decrypted"
#context['now'] = timezone.now()
return context
class EventDetailPollsView(EventDetailView):
template_name="polls/event_detail_polls.html"
template_name = "polls/event_detail_polls.html"
class EventDetailOrganisersView(EventDetailView):
template_name="polls/event_detail_organisers.html"
class EventDetailLaunchView(EventDetailView):
template_name="polls/event_detail_launch.html"
class EventDetailEntitiesView(EventDetailView):
template_name = "polls/event_detail_entities.html"
class EventDetailAdvancedView(EventDetailView):
template_name = "polls/event_detail_advanced.html"
class PollDetailView(generic.View):
model = Poll
def get_context_data(self, **kwargs):
context = super(PollDetailView, self).get_context_data(**kwargs)
#context['now'] = timezone.now()
context['form'] = VoteForm(instance=self.object)
context['poll_count'] = self.object.event.polls.all().count()
return context
#my_value = self.kwargs.get('key', 'default_value')
def test_poll_detail(request, event_id, poll_num, key=None):
context = {}
context['form'] = VoteForm(instance=self.object)
context['poll_count'] = self.object.event.polls.all().count()
return render(request, "polls/event_setup.html", context)
def util_get_poll_by_event_index(event, poll_num):
try:
@ -80,78 +73,114 @@ def util_get_poll_by_event_index(event, poll_num):
return None
return poll
def edit_poll(request, event_id, poll_num):
event = get_object_or_404(Event, pk=event_id)
event_poll_count = event.polls.all().count()
poll = util_get_poll_by_event_index(event, poll_num)
if (poll == None):
raise Http404("Poll does not exist")
form = PollForm(instance=poll, prefix="main")
formset = OptionFormset(instance=poll, prefix="formset_options")
return render(request, "polls/generic_form.html", {'form_title': "Edit Poll: " + poll.question_text, 'form': form, 'option_formset': formset})
if request.method == 'GET':
form = PollForm(instance=poll, prefix="main")
formset = OptionFormset(instance=poll, prefix="formset_options")
return render(request, "polls/generic_form.html", {'form_title': "Edit Poll: " + poll.question_text, 'form': form, 'option_formset': formset})
elif request.method == 'POST':
form = PollForm(request.POST, instance=poll, prefix="main")
def view_poll(request, event_id, poll_num):
#return HttpResponse(param("012345"))
#return HttpResponse(combpk(param("012345"), "ABzqvL+pqTi+DNLLRcM62RwCoaZTaXVbOs3sk4fc0+Dc 0 AAaQd6S1x+bcgnkDp2ev5mTt34ICQdZIzP9GaqG4x5sy 0" "ABhQay9jI4pZvkAETNwfo8iwJ8eBMkjqplqAiu/FZxMy 0 ABPxj0jVj3rt0VW54iv4tV02gYtujnR41t5gf97asrPs 0 ABfoiW03bsYIUgfAThmjurmOViKy9L89vfkIavhQIblm 1 ABhQay9jI4pZvkAETNwfo8iwJ8eBMkjqplqAiu/FZxMy 0 ABPxj0jVj3rt0VW54iv4tV02gYtujnR41t5gf97asrPs 0 ABfoiW03bsYIUgfAThmjurmOViKy9L89vfkIavhQIblm 1 ABhQay9jI4pZvkAETNwfo8iwJ8eBMkjqplqAiu/FZxMy 0 ABPxj0jVj3rt0VW54iv4tV02gYtujnR41t5gf97asrPs 0 ABfoiW03bsYIUgfAThmjurmOViKy9L89vfkIavhQIblm 1"))
#return HttpResponse(addec("ACMW70Yj3+mJ/FO+6VOSDGYPYHf7NoTXdpInbfzUqYpH 0 ABV4Mo496B0FW3AW/7gY6Fs+oz6BwfwilonMYeriUyV/ 0 AAg+bdGhs3sxSxAc/wcKdBNUy+el8A2b4yVYShNOb8uX 0 AAspJbn5V2AaY4CgLkzCkHwUWbC5nyxrBzw+o4Az8HVM 1 ABKI7o5Yhgi44XwpFnPpLnH0/czbXA8y5vM4ucV8vojo 1 AAwVrT9+dcQsqRZYoI7+QsJvWOgd7JaJpfI6envmC2jU 1 ABIZO0DK4OrdROD805of6iRk2RenonGYmo2qG2IB1sj/ 1 ACMUHQdjGN0wyCd2AgDHMk9u0TpnywNVtamHWopGho8L 0 ABNT5lbE4siC3QklQXRvTwSQPwtme91+UrIr9iXT3y84 1 ABib0mmQ9ZVCrErqFwDgoRp3jHPpjHGQR2vsMVlwM+vI 0 ABvf3cg1NSS8fn6EKJNnTomeoflcEY1WBxkPPKrBBFl+ 0 ACBUZAtolN4HNh+mw4jLZuHzD+/rYHKR5av16PUc6BJF 0", "2"))
#return HttpResponse(tally("ACNQLLQlh+lNm1Dc+X+dEI0ECVLTkxRHjRnzX1OA+HtW 0 AAWOsUZK/G/cjhUee/gPAXop3Bc0CTVG3iDdQxD6+XqV 0", "ACNQLLQlh+lNm1Dc+X+dEI0ECVLTkxRHjRnzX1OA+HtW 0 0 2", "2"))
if form.is_valid():
form.save()
formset = OptionFormset(request.POST, instance=poll, prefix="formset_options")
if formset.is_valid():
formset.save()
return HttpResponseRedirect(reverse('polls:event-polls', args=[poll.event_id]))
def event_vote(request, event_id, poll_num):
event = get_object_or_404(Event, pk=event_id)
if (not event.prepared):
if not event.prepared:
messages.add_message(request, messages.WARNING, "This Event isn\'t ready for voting yet.")
return HttpResponseRedirect(reverse("user_home"))
event_poll_count = event.polls.all().count()
prev_poll_index, next_poll_index = False, False
can_vote, has_voted, voter_email, vote_count = False, False, "", 0
can_vote, has_voted, voter_email = False, False, ""
poll = util_get_poll_by_event_index(event, poll_num)
if (poll == None):
raise Http404("Poll does not exist")
if poll is None:
messages.add_message(request, messages.ERROR, "There was an error loading the voting page.")
return HttpResponseRedirect(reverse("user_home"))
form = VoteForm(instance=poll)
poll_num = int(poll_num) # now known to be safe as it suceeded in the util function
poll_num = int(poll_num) # now known to be safe as it succeeded in the util function
if (poll_num > 1):
if poll_num > 1:
prev_poll_index = (poll_num - 1)
if (poll_num < event_poll_count):
if poll_num < event_poll_count:
next_poll_index = (poll_num + 1)
access_key = request.GET.get('key', None)
email_key = event.keys.filter(key=access_key)
vote_count = Ballot.objects.filter(poll=poll, cast=True).count()
if (email_key.exists() and event.voters.filter(email=email_key[0].user.email).exists()):
ballot = Ballot.objects.filter(voter=email_key[0].user, poll=poll)
if (ballot.exists() and ballot[0].cast):
has_voted = True
if (access_key and email_key.exists()): #or (can_vote(request.user, event))
ballot = None
if email_key.exists() and event.voters.filter(email=email_key[0].user.email).exists():
# Passing this test means the user can vote
voter_email = email_key[0].user.email
can_vote = True
if (request.method == "POST"):
form = VoteForm(request.POST, instance=poll)
if (email_key.exists()):
#return HttpResponse(email_key[0].key)
ballot = Ballot.objects.get_or_create(voter=email_key[0].user, poll=poll)[0]
# Check whether this is the first time a user is voting
ballot = Ballot.objects.filter(voter=email_key[0].user, poll=poll)
if ballot.exists() and ballot[0].cast:
has_voted = True
else:
messages.add_message(request, messages.ERROR, "You don\'t have permission to vote in this event.")
return HttpResponseRedirect(reverse("user_home"))
if (form.is_valid()):
ballot.cipher_text_c1 = request.POST["cipher_text_c1"]
ballot.cipher_text_c2 = request.POST["cipher_text_c2"]
ballot.cast = True
ballot.save()
if (next_poll_index):
return HttpResponseRedirect(reverse('polls:view-poll', kwargs={'event_id': event.id, 'poll_num': next_poll_index }) + "?key=" + email_key[0].key)
else:
return HttpResponse("Voted successfully!") # finished all polls in event
if request.method == "POST":
if ballot is None:
ballot = Ballot.objects.get_or_create(voter=email_key[0].user, poll=poll)
return render(request, "polls/poll_detail.html",
{"object": poll, "poll_num": poll_num , "event": event, "form": form, "poll_count": event.polls.all().count(),
"prev_index": prev_poll_index , "next_index": next_poll_index,
"can_vote": can_vote, "voter_email": voter_email, "has_voted": has_voted, "vote_count": vote_count
# Will store the fragments of the encoding scheme that define the vote
encrypted_vote = EncryptedVote.objects.get_or_create(ballot=ballot[0])[0]
# Clear any existing fragments - a voter changing their vote
encrypted_vote.fragment.all().delete()
# Add in the new ciphers
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[0].cast = True
ballot[0].save()
if next_poll_index:
return HttpResponseRedirect(reverse('polls:event-vote', kwargs={'event_id': event.id, 'poll_num': next_poll_index }) + "?key=" + email_key[0].key)
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 render(request, "polls/event_vote.html",
{
"object": poll, "poll_num": poll_num, "event": event, "poll_count": event.polls.all().count(),
"prev_index": prev_poll_index, "next_index": next_poll_index, "min_selection": poll.min_num_selections,
"max_selection": poll.max_num_selections, "can_vote": can_vote, "voter_email": voter_email,
"has_voted": has_voted
})
def event_trustee_setup(request, event_id):
# Obtain the event and the event preparation access key that's been supplied
event = get_object_or_404(Event, pk=event_id)
@ -178,68 +207,82 @@ def event_trustee_setup(request, event_id):
# The event will now be ready to receive votes on the various polls that have been defined -
# voters therefore need to be informed
if event.trustee_keys.count() == event.users_trustees.count():
create_ballots.delay(event)
generate_combpk.delay(event)
# TODO: Create Celery task that generates voting URLs for voters as well as creates the ballots
email_voters_vote_url.delay(event.voters.all(), event)
success_msg = 'You have successfully submitted your public key for this event'
success_msg = 'You have successfully submitted your public key for this event!'
messages.add_message(request, messages.SUCCESS, success_msg)
# This re-direct may not be appropriate for trustees that don't have logins
return HttpResponseRedirect(reverse("user_home"))
else:
form = EventSetupForm()
return render(request, "polls/event_setup.html", {"event": event, "form": form})
return render(request, "polls/event_setup.html", {"event": event, "form": form, "user_email": email_key[0].user.email})
#if no key or is invalid?
messages.add_message(request, messages.WARNING, 'You do not have permission to access: ' + request.path)
return HttpResponseRedirect(reverse("user_home"))
def event_addec(request, event_id):
def event_end(request, event_id):
event = get_object_or_404(Event, pk=event_id)
for poll in event.polls.all():
generate_enc.delay(poll)
return HttpResponse("Generating enc.")
if not event.ended:
event_ended.delay(event)
# Mark the event as ended
event.ended = True
event.save()
return HttpResponseRedirect(reverse('polls:view-event', args=[event_id]))
# Returns a JSONed version of the results
def results(request, event_id):
event = get_object_or_404(Event, pk=event_id)
polls = event.polls.all()
results = ""
results += "{\"polls\":["
for poll in polls:
results += poll.enc
results += "]}"
return HttpResponse(results)
def event_trustee_decrypt(request, event_id):
event = get_object_or_404(Event, pk=event_id)
access_key = request.GET.get('key', None)
if (access_key):
email_key = event.keys.filter(key=access_key)
if (email_key.exists() and event.users_trustees.filter(email=email_key[0].user.email).exists()):
if (Decryption.objects.filter(event=event, user=email_key[0].user).exists()):
messages.add_message(request, messages.WARNING, 'You have already provided your decryptions for this event')
#if (event.decryptions.count() == (event.polls.count() * event.users_trustees.count())):
# tally_results.delay(event) # all keys are in
return HttpResponseRedirect(reverse("user_home"))
elif (request.method == "GET"):
initial = []
for poll in event.polls.all():
initial.append({'text': poll.enc })
formset = DecryptionFormset(initial=initial)
else:
formset = DecryptionFormset(request.POST)
data = []
for form in formset:
if form.is_valid():
data.append(form.cleaned_data.get('text'))
if (len(data) == event.polls.count()):
for dec, poll in zip(data, event.polls.all()):
Decryption.objects.get_or_create(user=email_key[0].user, event=event, poll=poll, text=dec)
messages.add_message(request, messages.SUCCESS, 'Decryption complete.')
if (event.decryptions.count() == (event.polls.count() * event.users_trustees.count())):
tally_results.delay(event) # all keys are in
else:
messages.add_message(request, messages.ERROR, 'You didn\'t provide decryptions for every poll. Please try again.')
return HttpResponseRedirect(reverse("user_home"))
return render(request, "polls/event_decrypt.html", {"event": event, "formset": formset, "helper": DecryptionFormSetHelper() })
if access_key:
email_key = event.keys.filter(key=access_key)
if email_key.exists() and event.users_trustees.filter(email=email_key[0].user.email).exists():
if TrusteeSK.objects.filter(event=event, trustee=email_key[0].user).exists():
messages.add_message(request, messages.WARNING, 'You have already provided your decryption key for this event')
return HttpResponseRedirect(reverse("user_home"))
elif request.method == "GET":
return render(request, "polls/event_decrypt.html", {"event": event, "user_email": email_key[0].user.email})
elif request.method == "POST":
sk = request.POST['secret-key']
TrusteeSK.objects.create(event=event,
trustee=email_key[0].user,
key=sk)
if event.trustee_sk.count() == event.users_trustees.count():
# Generate the event SK and decrypt the event to tally the results
gen_event_sk_and_dec.delay(event)
messages.add_message(request, messages.SUCCESS, 'Your secret key has been successfully submitted')
return HttpResponseRedirect(reverse("user_home"))
# Without an access key, the client does not have permission to access this page
messages.add_message(request, messages.WARNING, 'You do not have permission to decrypt this Event.')
return HttpResponseRedirect(reverse("user_home"))
def test_poll_vote(request, poll_id):
poll = get_object_or_404(Poll, pk=poll_id)
form = VoteForm(instance=poll)
return render(request, "polls/vote_poll.html", {"vote_form": form, "poll": poll})
def manage_questions(request, event_id):
@ -262,7 +305,7 @@ def manage_questions(request, event_id):
formset = OptionFormset(request.POST, prefix="formset_organiser", instance=poll)
if formset.is_valid():
formset.save()
#create_ballots.delay(poll)
create_ballots_for_poll.delay(poll)
messages.add_message(request, messages.SUCCESS, 'Poll created successfully')
return HttpResponseRedirect(reverse('polls:event-polls', args=[poll.event_id]))
@ -274,6 +317,7 @@ def manage_questions(request, event_id):
else:
return HttpResponseNotAllowed()
def render_invalid(request, events, demo_users, invalid_fields):
return render(request,
"polls/create_event.html",
@ -285,6 +329,7 @@ def render_invalid(request, events, demo_users, invalid_fields):
"invalid_fields": invalid_fields
})
def create_event(request):
# Obtain context data for the rendering of the html template and validation
events = Event.objects.all()
@ -347,6 +392,7 @@ def create_event(request):
else:
return HttpResponseNotAllowed()
def edit_event(request, event_id):
event = get_object_or_404(Event, pk=event_id)
if request.method == "GET":
@ -384,7 +430,6 @@ def edit_event(request, event_id):
return render(request, "polls/generic_form.html", {"form_title": "Edit Event: " + event.title, "form": form}) #"organiser_formset": organiser_formset, "trustee_formset": trustee_formset})
#trustee_formset = TrusteeFormSet(request.POST, prefix="formset_trustee", instance=event)
#class CreatePoll(generic.View):
def del_event(request, event_id):
event = get_object_or_404(Event, pk=event_id)
@ -392,9 +437,4 @@ def del_event(request, event_id):
return render(request, "polls/del_event.html", {"event_title": event.title, "event_id": event.id})
elif request.method == "POST":
event.delete()
return HttpResponseRedirect(reverse('polls:index'))
def can_vote(user, event):
if event.voters.filter(email=user.email).exists():
return True
return False
return HttpResponseRedirect(reverse('polls:index'))

View File

@ -14,7 +14,7 @@
<hr>
<div class="row">
{% bootstrap_messages %}
{% if socialaccount_providers %}
<div class="col-md-5 col-lg-5">
{% include "allauth/account/provider_panel.html" with process="login" %}

View File

@ -18,6 +18,7 @@
crossorigin="anonymous"></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/decrypt_event.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>
@ -74,15 +75,19 @@
//new function
demosEncrypt.encryptAndSubmit = function() {
var ctx = new CTX("BN254CX"); //new context we can use
// 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 param = $('#event-param').val();
//console.log(param);
var parameter = $('#event-param').val();
var tempParams = JSON.parse(JSON.parse(parameter).crypto);
var tempParams = JSON.parse(param);
//copying the values
n.copy(tempParams.n);
g1.copy(tempParams.g1);
@ -92,26 +97,55 @@
n:n,
g1:g1,
g2:g2
}
var tempPK = JSON.parse($('#comb_pk').val());
};
var tempPK = JSON.parse($('#comb_pk').val());
var pk = new ctx.ECP(0);
pk.copy(tempPK.PK);
var answer = $('#poll-options').val();
console.log(answer);
var cipher = encrypt(params, pk, answer);
var c1Bytes = [];
cipher.C1.toBytes(c1Bytes);
var c2Bytes = [];
cipher.C2.toBytes(c2Bytes);
$('#id_cipher_text_c1').val(c1Bytes.toString());
$('#id_cipher_text_c2').val(c2Bytes.toString());
// 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();
}
};
//new function
@ -149,7 +183,7 @@
//new function
demosEncrypt.generateKeys = function() {
parameter = $("#event-param").val();
var parameter = $("#event-param").val();
var tempParams = JSON.parse(JSON.parse(parameter).crypto);
//the full objects need to be initalised as per the library, then copy the values we need into it
//I follow Bingsheng's code as to what objects are used in the parameter object

View File

@ -7,7 +7,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}dẽmos 2{% endblock %}</title>
<title>{% block title %}DĒMOS 2{% endblock %}</title>
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">

View File

@ -7,24 +7,24 @@
{% block content %}
<div class="container">
<h1>Event: {{event.title}}</h1>
<h2>Trustee Decrypt</h2>
<h2>Trustee Event Decryption for Event '{{ event.title }}'</h2>
<hr/>
<div class="panel panel-default">
<div class="panel-heading">Secret Key</div>
<div class="panel-heading"><strong>Submit your Secret Key as '{{ user_email }}'</strong></div>
<div class="panel panel-body">
<input id="secret-key" class="textinput textInput form-control" type="text"></input>
<p>Use your secret key to generate a decrypted cipher</p>
<button id="keygen-btn" onclick="demosEncrypt.decryptCipher()" class="btn btn-default">Decrypt</button>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">Encrypted Ciphers</div>
<div class="panel panel-body">
{% load crispy_forms_tags %}
<form method="post" action="" class="">
{% crispy formset helper %}
<input class="btn btn-default" type="submit" value="Submit" disabled>
</form>
<form id="sk-form" method="POST">
{% csrf_token %}
<input id="secret-key" name="secret-key" class="textinput textInput form-control" type="text"/>
<div class="alert alert-info" role="alert" style="margin-top: 0.75em;">
Your secret key will be used to decrypt the event and get a vote tally for every poll.
</div>
<label for="files_sk_upload" class="btn btn-primary">
<span class="glyphicon glyphicon-cloud-upload"></span>
Upload Key
</label>
<input type="file" id="files_sk_upload" name="file" class="btn-info">
<input type="submit" value="Submit" class="btn btn-success"/>
</form>
</div>
</div>
</div>

View File

@ -9,12 +9,22 @@
{% if is_organiser %}
<div>
<!-- Heading -->
<div class="col-xs-7 col-sm-9 col-md-10">
<div class="col-xs-7 col-sm-9 col-md-9">
<h2>Event: {{object.title}}</h2>
</div>
<!-- Edit Button -->
<div class="col-xs-5 col-sm-3 col-md-2 marginTopEditButton">
<a href="{% url 'polls:edit-event' event.id %}" class="btn btn-primary" style="float: right;">
<div class="col-xs-5 col-sm-3 col-md-3 marginTopEditButton">
{% if object.has_received_votes and object.ended == False %}
<a href="{% url 'polls:end-event' event.id %}" class="btn btn-danger" style="float: right;">
<span class="glyphicon glyphicon-stop"></span> End
</a>
{% endif %}
{% if decrypted == True and object.ended == True %}
<a href="{% url 'polls:event-results' event.id %}" class="btn btn-success" style="float: right;">
<span class="glyphicon glyphicon-stats"></span> Results
</a>
{% endif %}
<a href="{% url 'polls:edit-event' event.id %}" class="btn btn-primary" style="float: right; margin-right: 0.4em;">
<span class="fa fa-pencil"></span> Edit
</a>
</div>
@ -40,11 +50,11 @@
<a href="{% url 'polls:event-polls' event.id %}"><strong>Polls ({{ object.polls.count }})</strong></a>
</li>
<li class="{% block event_nav_organisers %}{% endblock %}">
<a href="{% url 'polls:event-organisers' event.id %}"><strong>Entities</strong></a>
<a href="{% url 'polls:event-entities' event.id %}"><strong>Entities</strong></a>
</li>
{% if is_organiser %}
<li class="{% block event_nav_launch %}{% endblock %}">
<a href="{% url 'polls:launch-event' event.id %}"><strong>Advanced</strong></a>
<a href="{% url 'polls:event-advanced' event.id %}"><strong>Advanced</strong></a>
</li>
{% endif %}
</ul>

View File

@ -6,7 +6,7 @@
{% block event_content %}
{% if object.polls.all %}
{% for poll in object.polls.all %}
<h3>Poll: {{ poll.question_text }} (<a href="{% url 'polls:view-poll' event_id=event.id poll_num=forloop.counter %}">Edit</a>)</h3>
<h3>Poll: {{ poll.question_text }} (<a href="{% url 'polls:edit-poll' event_id=event.id poll_num=forloop.counter %}">Edit</a>)</h3>
<br/>
<h4>Poll Options:</h4>
<ul class="list-group">

View File

@ -46,9 +46,13 @@
</td>
<td class="text-center">
<div class="btn statusBtn
{% if event.status == 'Expired' %}btn-danger{% endif %}
{% if event.status == 'Future' %}btn-info{% endif %}
{% if event.status == 'Prepared' %}btn-info{% endif %}
{% if event.status == 'Active' %}btn-success{% endif %}
{% if event.status == 'Future' %}btn-info{% endif %}">
{% if event.status == 'Expired' %}btn-danger{% endif %}
{% if event.status == 'Ended' %}btn-danger{% endif %}
{% if event.status == 'Decrypted' %}btn-primary{% endif %}
">
{{ event.status }}
</div>
</td>

View File

@ -9,6 +9,8 @@
<div class="container">
<h2>Trustee Event Setup for Event '{{ event.title }}'</h2>
<hr/>
<h4>Key Generation For: {{ user_email }}</h4>
<br/>
<div class="panel panel-default">
<div class="panel-heading"><strong>Step 1: Generate Your Secret Key</strong></div>
<div class="panel panel-body">

View File

@ -0,0 +1,81 @@
{% extends "bases/bootstrap-with-nav.html" %}
{% load staticfiles %}
{% load bootstrap3 %}
{% block app_js_vars %}
var option_count = {{ object.options.count }};
{% endblock %}
{% block content %}
<div class="container">
<input id="event-param" type="text" value="{{event.EID}}" hidden/>
<input id="comb_pk" type="text" value="{{event.public_key}}" hidden/>
<!-- TODO: Add warning not to share the URL-->
<h2>Event Voting Page for the Event '{{ object.event.title }}'</h2>
<div class="alert alert-warning" role="alert" style="margin-top: 1em;">
You are voting as: <strong>{{ voter_email }}</strong> - Ensure this is correct and don't share this URL!
</div>
<span><strong>Voting status:</strong>
{% if has_voted %}
Voted - Re-Submitting will Change your Vote
{% else %}
Not Voted
{% endif %}
</span>
<br/>
<span><strong>Number of polls for this event:</strong> {{ poll_count }}</span>
<br/>
<br/>
<span><strong>Instructions:</strong>
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>
minimum</strong> of {{ min_selection }} option selection(s) and a <strong>maximum</strong> of
{{ max_selection }}. Please make your choice below.
</span>
<div class="panel panel-body">
{% if prev_index %}
<a href="{% url 'polls:event-vote' event_id=object.event.id poll_num=prev_index %}" class="btn" role="button">
<span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>
</a>
{% endif %}
{% if next_index %}
<a href="{% url 'polls:event-vote' event_id=object.event.id poll_num=next_index %}" class="btn" role="button">
<span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>
</a>
{% endif %}
</div>
{% if object.options.all %}
<h3>Poll {{ poll_num }} of {{ poll_count }}: {{object.question_text}}</h3>
{% if can_vote %}
{% load crispy_forms_tags %}
<div class="panel panel-default">
<div class="panel-heading"><strong>Options</strong></div>
<div class="panel panel-body">
<select class="radio-inline select form-control" id="poll-options" name="options">
{% load custom_filters_tags %}
<option value="{{ -1|get_ballot_value:object.options.all.count }}">Please Select...</option>
{% for option in object.options.all %}
<option value="{{forloop.counter|get_ballot_value:object.options.all.count}}">{{ option.choice_text }}</option>
{% endfor %}
</select>
<hr/>
<button id="keygen-btn" onclick="demosEncrypt.encryptAndSubmit()" class="btn btn-primary">Submit</button>
<form id="cipher-form" method="post" action="" class="">
{% csrf_token %}
</form>
</div>
</div>
{% else %}
<div class="alert alert-warning" role="alert">
<p>You don't have permission to vote in this event.</p>
</div>
{% endif %}
{% else %}
<p>No options are available.</p>
{% endif %}
</div>
<br>
<br>
{% endblock %}

View File

@ -1,85 +0,0 @@
{% extends "bases/bootstrap-with-nav.html" %}
{% load staticfiles %}
{% load bootstrap3 %}
{% block app_js_vars %}
var option_count = {{ object.options.count }};
{% endblock %}
{% block content %}
<div class="container">
<input id="event-param" type="text" value="{{event.EID}}" hidden></input>
<input id="comb_pk" type="text" value="{{event.public_key}}" hidden></input>
<h1>Poll: {{object.question_text}}</h1>
<span>Poll {{ poll_num }} of {{ poll_count }} in Event: <a href="{% url 'polls:view-event' object.event.id %}">{{ object.event.title }}</a></span>
<div class="panel panel-body">
{% if prev_index %}
<a href="{% url 'polls:view-poll' event_id=object.event.id poll_num=prev_index %}" class="btn" role="button">
<span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>
</a>
{% endif %}
{% if next_index %}
<a href="{% url 'polls:view-poll' event_id=object.event.id poll_num=next_index %}" class="btn" role="button">
<span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>
</a>
{% endif %}
</div>
<a href="{% url 'polls:edit-poll' event_id=object.event.id poll_num=poll_num %}"><span class="fa fa-pencil"></span> Edit Poll</a>
{% if object.options.all %}
<h3>Options</h3>
<p> {{ vote_count }} vote(s) have been cast</p>
{% if can_vote %}
{% if has_voted %}
<p>You have already voted in this poll. Resubmitting the form will change your vote.</p>
{% endif %}
<p>Voting as {{ voter_email }} -- Do NOT share this url</p>
{% load crispy_forms_tags %}
<div class="panel panel-default">
<div class="panel-heading">Options</div>
<div class="panel panel-body">
<select class="radio-inline select form-control" id="poll-options" name="options">
{% load custom_filters_tags %}
{% for option in object.options.all %}
<option value="{{forloop.counter|get_ballot_value}}">{{ option.choice_text }}</option>
{% endfor %}
</select>
<button id="keygen-btn" onclick="demosEncrypt.encryptAndSubmit()" class="btn btn-default">Encrypt & Submit</button>
<form id="cipher-form" method="post" action="" class="">
{% crispy form %}
{% csrf_token %}
</form>
</div>
</div>
{% else %}
<div class="alert alert-warning" role="alert">
<p>You do not have permission to vote in this Event.</p>
</div>
{% endif %}
{% else %}
<p>No options are available.</p>
{% endif %}
</div>
<br>
<br>
POLL ENC {{ object.enc }}
{% if form.errors %}
{% for field in form %}
{% for error in field.errors %}
<div class="alert alert-danger">
<strong>{{ error|escape }}</strong>
</div>
{% endfor %}
{% endfor %}
{% for error in form.non_field_errors %}
<div class="alert alert-danger">
<strong>{{ error|escape }}</strong>
</div>
{% endfor %}
{% endif %}
{% endblock %}

View File

@ -157,7 +157,7 @@ input[type="file"] {
/* Events List page / Events Detail */
.statusBtn {
width: 74px;
width: 89px;
}
.marginTopEventList {

View File

@ -0,0 +1,20 @@
function processFileSKChange(event) {
var files = event.target.files;
if(files !== undefined
&& files[0] !== undefined) {
var reader = new FileReader();
reader.onload = function(e) {
$('input#secret-key').val(reader.result);
};
reader.readAsText(files[0]);
}
}
var filesHandleSK = document.getElementById('files_sk_upload');
if(filesHandleSK) {
filesHandleSK.addEventListener('change', processFileSKChange, false);
}