Support null-encrypted election in web UI

This commit is contained in:
RunasSudo 2021-10-16 20:13:28 +11:00
parent adbdb21e0d
commit d44c21cbd7
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
14 changed files with 5647 additions and 2810 deletions

View File

@ -1,5 +1,5 @@
# Eos - Verifiable elections # Eos - Verifiable elections
# Copyright © 2017-2019 RunasSudo (Yingtong Li) # Copyright © 2017-2021 RunasSudo (Yingtong Li)
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by # it under the terms of the GNU Affero General Public License as published by
@ -30,6 +30,9 @@ class NullEncryptedAnswer(EncryptedAnswer):
def decrypt(self): def decrypt(self):
return None, self.answer return None, self.answer
def deaudit(self):
return self
class Ballot(EmbeddedObject): class Ballot(EmbeddedObject):
#_id = UUIDField() #_id = UUIDField()
encrypted_answers = EmbeddedObjectListField() encrypted_answers = EmbeddedObjectListField()
@ -219,6 +222,10 @@ class Election(TopLevelObject):
questions = EmbeddedObjectListField() questions = EmbeddedObjectListField()
results = EmbeddedObjectListField(is_hashed=False) results = EmbeddedObjectListField(is_hashed=False)
def can_audit(self):
"""Can prepared votes be audited?"""
return False
def verify(self): def verify(self):
#__pragma__('skip') #__pragma__('skip')
from eos.core.hashing import SHA256 from eos.core.hashing import SHA256

View File

@ -1,5 +1,5 @@
# Eos - Verifiable elections # Eos - Verifiable elections
# Copyright © 2017-18 RunasSudo (Yingtong Li) # Copyright © 2017-2021 RunasSudo (Yingtong Li)
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by # it under the terms of the GNU Affero General Public License as published by
@ -38,7 +38,7 @@ class ElectionTestCase(EosTestCase):
def test_run_election(self): def test_run_election(self):
# Set up election # Set up election
election = Election() election = Election()
election.workflow = WorkflowBase() election.workflow = BaseWorkflow()
# Check _instance # Check _instance
self.assertEqual(election.workflow._instance, (election, 'workflow')) self.assertEqual(election.workflow._instance, (election, 'workflow'))

View File

@ -1,5 +1,5 @@
# Eos - Verifiable elections # Eos - Verifiable elections
# Copyright © 2017-18 RunasSudo (Yingtong Li) # Copyright © 2017-2021 RunasSudo (Yingtong Li)
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by # it under the terms of the GNU Affero General Public License as published by
@ -58,7 +58,12 @@ class WorkflowTask(EmbeddedObject):
def are_dependencies_met(self): def are_dependencies_met(self):
for depends_on_desc in self.depends_on: for depends_on_desc in self.depends_on:
for depends_on_task in self.workflow.get_tasks(depends_on_desc): depends_on_tasks = list(self.workflow.get_tasks(depends_on_desc))
if len(depends_on_tasks) == 0:
return False
for depends_on_task in depends_on_tasks:
if depends_on_task.status is not WorkflowTaskStatus.EXITED: if depends_on_task.status is not WorkflowTaskStatus.EXITED:
return False return False
return True return True
@ -184,7 +189,9 @@ class TaskReleaseResults(WorkflowTask):
# Concrete workflows # Concrete workflows
# ================== # ==================
class WorkflowBase(Workflow): class BaseWorkflow(Workflow):
"""Base workflow, with no encryption"""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)

View File

@ -1,5 +1,5 @@
# Eos - Verifiable elections # Eos - Verifiable elections
# Copyright © 2017 RunasSudo (Yingtong Li) # Copyright © 2017-2021 RunasSudo (Yingtong Li)
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by # it under the terms of the GNU Affero General Public License as published by
@ -230,7 +230,12 @@ class PSRElection(Election):
public_key = EmbeddedObjectField(SEGPublicKey) public_key = EmbeddedObjectField(SEGPublicKey)
mixing_trustees = EmbeddedObjectListField() mixing_trustees = EmbeddedObjectListField()
def can_audit(self):
"""Overrides Election.can_audit"""
return True
def verify(self): def verify(self):
"""Overrides Election.verify"""
# Verify ballots # Verify ballots
super().verify() super().verify()

View File

@ -1,6 +1,6 @@
/* /*
Eos - Verifiable elections Eos - Verifiable elections
Copyright © 2017-2019 RunasSudo (Yingtong Li) Copyright © 2017-2021 RunasSudo (Yingtong Li)
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by it under the terms of the GNU Affero General Public License as published by
@ -22,17 +22,34 @@ isLibrariesLoaded = false;
eosjs = null; eosjs = null;
function generateEncryptedVote(election, answers, should_do_fingerprint) { function generateEncryptedVote(election, answers, should_do_fingerprint) {
encrypted_answers = []; if (election._name === 'eos.psr.election.PSRElection') {
for (var q_num = 0; q_num < answers.length; q_num++) { encrypted_answers = [];
answer_json = answers[q_num]; for (var q_num = 0; q_num < answers.length; q_num++) {
answer = eosjs.eos.core.objects.EosObject.deserialise_and_unwrap(answer_json, null); answer_json = answers[q_num];
encrypted_answer = eosjs.eos.psr.election.BlockEncryptedAnswer.encrypt(election.public_key, answer, election.questions.__getitem__(q_num).max_bits() + 32); // +32 bits for the length answer = eosjs.eos.core.objects.EosObject.deserialise_and_unwrap(answer_json, null);
encrypted_answers.push(eosjs.eos.core.objects.EosObject.serialise_and_wrap(encrypted_answer, null)); encrypted_answer = eosjs.eos.psr.election.BlockEncryptedAnswer.encrypt(election.public_key, answer, election.questions.__getitem__(q_num).max_bits() + 32); // +32 bits for the length
} encrypted_answers.push(eosjs.eos.core.objects.EosObject.serialise_and_wrap(encrypted_answer, null));
}
postMessage({ postMessage({
encrypted_answers: encrypted_answers encrypted_answers: encrypted_answers
}); });
} else if (election._name === 'eos.base.election.Election') {
encrypted_answers = [];
for (var q_num = 0; q_num < answers.length; q_num++) {
answer_json = answers[q_num];
answer = eosjs.eos.core.objects.EosObject.deserialise_and_unwrap(answer_json, null);
encrypted_answer = eosjs.eos.base.election.NullEncryptedAnswer();
encrypted_answer.answer = answer;
encrypted_answers.push(eosjs.eos.core.objects.EosObject.serialise_and_wrap(encrypted_answer, null));
}
postMessage({
encrypted_answers: encrypted_answers
});
} else {
throw "Don't know how to encrypt ballots in election of type " + election._name;
}
} }
onmessage = function(msg) { onmessage = function(msg) {

View File

@ -1,6 +1,6 @@
{# {#
Eos - Verifiable elections Eos - Verifiable elections
Copyright © 2017-2019 RunasSudo (Yingtong Li) Copyright © 2017-2021 RunasSudo (Yingtong Li)
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by it under the terms of the GNU Affero General Public License as published by
@ -32,9 +32,17 @@
{% elif template == 'booth/audit.html' %} {% elif template == 'booth/audit.html' %}
{% set menuindex = 4 %} {% set menuindex = 4 %}
{% elif template == 'booth/cast.html' %} {% elif template == 'booth/cast.html' %}
{% set menuindex = 5 %} {% if election.can_audit() %}
{% set menuindex = 5 %}
{% else %}
{% set menuindex = 4 %}
{% endif %}
{% elif template == 'booth/complete.html' %} {% elif template == 'booth/complete.html' %}
{% set menuindex = 6 %} {% if election.can_audit() %}
{% set menuindex = 6 %}
{% else %}
{% set menuindex = 5 %}
{% endif %}
{% endif %} {% endif %}
{% macro menuitem(index, text) %} {% macro menuitem(index, text) %}
@ -50,9 +58,14 @@
{{ menuitem(1, "Welcome") }} {{ menuitem(1, "Welcome") }}
{{ menuitem(2, "Select") }} {{ menuitem(2, "Select") }}
{{ menuitem(3, "Review") }} {{ menuitem(3, "Review") }}
{{ menuitem(4, "Audit") }} {% if election.can_audit() %}
{{ menuitem(5, "Cast") }} {{ menuitem(4, "Audit") }}
{{ menuitem(6, "Finish") }} {{ menuitem(5, "Cast") }}
{{ menuitem(6, "Finish") }}
{% else %}
{{ menuitem(4, "Cast") }}
{{ menuitem(5, "Finish") }}
{% endif %}
</ul> </ul>
<div class="ui container"> <div class="ui container">

View File

@ -2,7 +2,7 @@
{# {#
Eos - Verifiable elections Eos - Verifiable elections
Copyright © 2017-2019 RunasSudo (Yingtong Li) Copyright © 2017-2021 RunasSudo (Yingtong Li)
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by it under the terms of the GNU Affero General Public License as published by
@ -20,7 +20,7 @@
{% block content %} {% block content %}
<div id="cast_prompt"> <div id="cast_prompt">
<p>Your vote has <span class="superem">not</span> yet been cast. Please make a note of your ballot fingerprint, <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64(true) }}</span>.</p> <p>Your vote has <span class="superem">not</span> yet been cast.{% if election.can_audit() %} Please make a note of your ballot fingerprint, <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64(true) }}</span>.{% endif %}</p>
<div class="ui negative message"> <div class="ui negative message">
<p>Your vote has <span class="superem">not</span> yet been cast. Please follow the instructions to continue.</p> <p>Your vote has <span class="superem">not</span> yet been cast. Please follow the instructions to continue.</p>
@ -69,10 +69,12 @@
{% endblock %} {% endblock %}
{% block after %} {% block after %}
<div class="ui tiny message" style="margin-top: 3em;"> {% if election.can_audit() %}
<div class="header">Information for advanced users</div> <div class="ui tiny message" style="margin-top: 3em;">
<p>Your full ballot fingerprint is <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64() }}</span>.</p> <div class="header">Information for advanced users</div>
</div> <p>Your full ballot fingerprint is <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64() }}</span>.</p>
</div>
{% endif %}
<script> <script>
$(".message .close").on("click", function() { $(".message .close").on("click", function() {

View File

@ -2,7 +2,7 @@
{# {#
Eos - Verifiable elections Eos - Verifiable elections
Copyright © 2017-2019 RunasSudo (Yingtong Li) Copyright © 2017-2021 RunasSudo (Yingtong Li)
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by it under the terms of the GNU Affero General Public License as published by
@ -23,7 +23,9 @@
<p>Your vote has <span class="superem">not</span> yet been cast. Please follow the instructions to continue.</p> <p>Your vote has <span class="superem">not</span> yet been cast. Please follow the instructions to continue.</p>
</div> </div>
<p>Please make a note of your ballot fingerprint, <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64(true) }}</span>. Please retain a copy of your ballot fingerprint – you can use it to verify that your vote has been counted correctly. You may <a href="#" onclick="window.print();return false;">print this page</a> as a receipt if you wish.</p> {% if election.can_audit() %}
<p>Please make a note of your ballot fingerprint, <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64(true) }}</span>. Please retain a copy of your ballot fingerprint – you can use it to verify that your vote has been counted correctly. You may <a href="#" onclick="window.print();return false;">print this page</a> as a receipt if you wish.</p>
{% endif %}
<p>To continue, copy and paste the ballot below and provide it to the election administrator.</p> <p>To continue, copy and paste the ballot below and provide it to the election administrator.</p>
@ -39,10 +41,12 @@
{% endblock %} {% endblock %}
{% block after %} {% block after %}
<div class="ui tiny message" style="margin-top: 3em;"> {% if election.can_audit() %}
<div class="header">Information for advanced users</div> <div class="ui tiny message" style="margin-top: 3em;">
<p>Your full ballot fingerprint is <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64() }}</span>.</p> <div class="header">Information for advanced users</div>
</div> <p>Your full ballot fingerprint is <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64() }}</span>.</p>
</div>
{% endif %}
{% endblock %} {% endblock %}
{% block help %} {% block help %}

View File

@ -2,7 +2,7 @@
{# {#
Eos - Verifiable elections Eos - Verifiable elections
Copyright © 2017-2019 RunasSudo (Yingtong Li) Copyright © 2017-2021 RunasSudo (Yingtong Li)
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by it under the terms of the GNU Affero General Public License as published by
@ -26,13 +26,16 @@
<div class="content"> <div class="content">
<div class="header">Smart ballot tracker</div> <div class="header">Smart ballot tracker</div>
<p>This smart ballot tracker confirms that {{ voter.py_name }} cast a vote in the election {{ election.py_name }} at {{ vote.cast_at }}.</p> <p>This smart ballot tracker confirms that {{ voter.py_name }} cast a vote in the election {{ election.py_name }} at {{ vote.cast_at }}.</p>
<p>Ballot fingerprint: <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(vote.ballot).hash_as_b64(true) }}</span></p> {% if election.can_audit() %}
<p>Ballot fingerprint: <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(vote.ballot).hash_as_b64(true) }}</span></p>
{% endif %}
</div> </div>
</div> </div>
<p>Please check that the ballot fingerprint above matches the ballot fingerprint you recorded earlier.</p> {% if election.can_audit() %}
<p>Please check that the ballot fingerprint above matches the ballot fingerprint you recorded earlier.</p>
<p>To confirm that your ballot was cast correctly, please go to the <a href="{{ election_base_url }}view/ballots">‘Voters and ballots’ page</a> for the {{ election.kind }} or click ‘Finish’, and confirm that the above ballot fingerprint appears next to your name.</p> <p>To confirm that your ballot was cast correctly, please go to the <a href="{{ election_base_url }}view/ballots">‘Voters and ballots’ page</a> for the {{ election.kind }} or click ‘Finish’, and confirm that the above ballot fingerprint appears next to your name.</p>
{% endif %}
{% endblock %} {% endblock %}
{% block buttons %} {% block buttons %}
@ -40,10 +43,12 @@
{% endblock %} {% endblock %}
{% block after %} {% block after %}
<div class="ui tiny message" style="margin-top: 3em;"> {% if election.can_audit() %}
<div class="header">Information for advanced users</div> <div class="ui tiny message" style="margin-top: 3em;">
<p>Your full ballot fingerprint is <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(vote.ballot).hash_as_b64() }}</span>.</p> <div class="header">Information for advanced users</div>
</div> <p>Your full ballot fingerprint is <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(vote.ballot).hash_as_b64() }}</span>.</p>
</div>
{% endif %}
{% endblock %} {% endblock %}
{% block help %} {% block help %}

View File

@ -2,7 +2,7 @@
{# {#
Eos - Verifiable elections Eos - Verifiable elections
Copyright © 2017-2019 RunasSudo (Yingtong Li) Copyright © 2017-2021 RunasSudo (Yingtong Li)
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by it under the terms of the GNU Affero General Public License as published by
@ -30,8 +30,12 @@
{% include templates[selection_model_view_map[election.questions.__getitem__(loop.index0)._name]["selections_review"]] %} {% include templates[selection_model_view_map[election.questions.__getitem__(loop.index0)._name]["selections_review"]] %}
{% endfor %} {% endfor %}
<p>If you are happy with your selections, then make a note of your ballot fingerprint, <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64(true) }}</span>.</p> {% if election.can_audit() %}
<p>Click ‘Continue’, and you will be able to log in to cast your vote.</p> <p>If you are happy with your selections, then make a note of your ballot fingerprint, <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64(true) }}</span>.</p>
<p>Click ‘Continue’, and you will be able to log in to cast your vote.</p>
{% else %}
<p>If you are happy with your selections, then click ‘Continue’, and you will be able to log in to cast your vote.</p>
{% endif %}
{% endblock %} {% endblock %}
{% block buttons %} {% block buttons %}
@ -40,11 +44,13 @@
{% endblock %} {% endblock %}
{% block after %} {% block after %}
<div class="ui tiny message" style="margin-top: 3em;"> {% if election.can_audit() %}
<div class="header">Information for advanced users</div> <div class="ui tiny message" style="margin-top: 3em;">
<p>Your ballot fingerprint is <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64() }}</span>.</p> <div class="header">Information for advanced users</div>
<p>If you would like to audit your ballot, <a href="#" onclick="nextTemplate(1);">click here</a></p> <p>Your ballot fingerprint is <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64() }}</span>.</p>
</div> <p>If you would like to audit your ballot, <a href="#" onclick="nextTemplate(1);">click here</a></p>
</div>
{% endif %}
{% endblock %} {% endblock %}
{% block help %} {% block help %}

View File

@ -2,7 +2,7 @@
{# {#
Eos - Verifiable elections Eos - Verifiable elections
Copyright © 2017-2019 RunasSudo (Yingtong Li) Copyright © 2017-2021 RunasSudo (Yingtong Li)
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by it under the terms of the GNU Affero General Public License as published by
@ -30,8 +30,12 @@
{% include templates[selection_model_view_map[election.questions.__getitem__(loop.index0)._name]["selections_review"]] %} {% include templates[selection_model_view_map[election.questions.__getitem__(loop.index0)._name]["selections_review"]] %}
{% endfor %} {% endfor %}
<p>If you are happy with your selections, then make a note of your ballot fingerprint, <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64(true) }}</span>.</p> {% if election.can_audit() %}
<p>Click ‘Continue’, and you will be able to copy your pre-poll ballot to provide to the election administrator.</p> <p>If you are happy with your selections, then make a note of your ballot fingerprint, <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64(true) }}</span>.</p>
<p>Click ‘Continue’, and you will be able to copy your pre-poll ballot to provide to the election administrator.</p>
{% else %}
<p>If you are happy with your selections, then click ‘Continue’, and you will be able to copy your pre-poll ballot to provide to the election administrator.</p>
{% endif %}
{% endblock %} {% endblock %}
{% block buttons %} {% block buttons %}

View File

@ -212,12 +212,14 @@
} }
}); });
templates['booth/welcome.html'] = null; templates['booth/welcome.html'] = null;
boothTasks.append({ boothTasks.append({
activate: function(fromLeft) { activate: function(fromLeft) {
showTemplate('booth/selections.html'); showTemplate('booth/selections.html');
} }
}); });
templates['booth/selections.html'] = null; templates['booth/selections.html'] = null;
boothTasks.append({ boothTasks.append({
activate: function(fromLeft) { activate: function(fromLeft) {
if (fromLeft) { if (fromLeft) {
@ -237,12 +239,14 @@
} }
}); });
templates['booth/review_prepoll.html'] = null; templates['booth/review_prepoll.html'] = null;
boothTasks.append({ boothTasks.append({
activate: function(fromLeft) { activate: function(fromLeft) {
showTemplate('booth/audit.html', {ballot: booth.ballot}); showTemplate('booth/audit.html', {ballot: booth.ballot});
} }
}); });
templates['booth/audit.html'] = null; templates['booth/audit.html'] = null;
boothTasks.append({ boothTasks.append({
activate: function(fromLeft) { activate: function(fromLeft) {
showTemplate('booth/cast_prepoll.html', {ballot: booth.ballot}); showTemplate('booth/cast_prepoll.html', {ballot: booth.ballot});
@ -257,18 +261,21 @@
} }
}); });
templates['booth/review.html'] = null; templates['booth/review.html'] = null;
boothTasks.append({ boothTasks.append({
activate: function(fromLeft) { activate: function(fromLeft) {
showTemplate('booth/audit.html', {ballot: booth.ballot}); showTemplate('booth/audit.html', {ballot: booth.ballot});
} }
}); });
templates['booth/audit.html'] = null; templates['booth/audit.html'] = null;
boothTasks.append({ boothTasks.append({
activate: function(fromLeft) { activate: function(fromLeft) {
showTemplate('booth/cast.html', {ballot: booth.ballot, is_cast: false}); showTemplate('booth/cast.html', {ballot: booth.ballot, is_cast: false});
} }
}); });
templates['booth/cast.html'] = null; templates['booth/cast.html'] = null;
boothTasks.append({ boothTasks.append({
activate: function(fromLeft) { activate: function(fromLeft) {
showTemplate('booth/complete.html', {voter: booth.voter, vote: booth.vote}); showTemplate('booth/complete.html', {voter: booth.voter, vote: booth.vote});
@ -287,6 +294,7 @@
} }
}); });
templates['booth/cast.html'] = null; templates['booth/cast.html'] = null;
boothTasks.append({ boothTasks.append({
activate: function(fromLeft) { activate: function(fromLeft) {
showTemplate('booth/complete.html', {voter: booth.voter, vote: booth.vote}); showTemplate('booth/complete.html', {voter: booth.voter, vote: booth.vote});

8258
package-lock.json generated

File diff suppressed because it is too large Load Diff

9
package.json Normal file
View File

@ -0,0 +1,9 @@
{
"dependencies": {
"@babel/cli": "^7.15.7",
"@babel/core": "^7.15.8",
"@babel/preset-env": "^7.15.8",
"babelify": "^10.0.0",
"browserify": "^17.0.0"
}
}