Eos/eosweb/core/templates/election/auditor.html
Yingtong Li a47164fc53
Use truncated hashes for votes #9
Also add a nice icon to the smart ballot tracker
2018-01-08 20:16:28 +08:00

220 lines
9.5 KiB
HTML

{% extends 'base.html' %}
{#
Eos - Verifiable elections
Copyright © 2017-18 RunasSudo (Yingtong Li)
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
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
#}
{% block title %}Ballot auditor{% endblock %}
{% block content %}
<h2>Ballot auditor</h2>
<div class="ui form">
<div class="field">
<label for="audit_election_url">Election URL</label>
<input id="audit_election_url" placeholder="https://example.com/01234567-89ab-cdef-ghij-klmnopqrstuv" type="text">
</div>
<div class="ui error message" id="audit_error_load">
<p>There was an error loading the election data. Please check the election URL and try again. If the problem persists, contact the election administrator.</p>
<p class="techdetails"></p>
</div>
<div class="field">
<label for="audit_ballot">Ballot data</label>
<textarea id="audit_ballot" placeholder='{"type": "eos.base.election.AuditBallot", "value": …}'></textarea>
</div>
<button class="ui primary button" onclick="loadElection();">Audit ballot</button>
</div>
<div class="ui hidden warning message" id="audit_contents">
<div class="header">Ballot contents</div>
<div id="audit_contents_inner"></div>
</div>
<div class="ui hidden negative message" id="audit_not_yet_cast">
<p>Your vote has <span class="superem">not</span> yet been cast. If you are satisfied with your ballot, you must return to the voting booth by closing this page and following the instructions.</p>
</div>
<div class="ui hidden message" id="audit_result">
<div class="header">Audit result</div>
<div id="audit_result_inner"></div>
</div>
{% endblock %}
{% block basecontent %}
{{ super() }}
<script src="{{ url_for('static', filename='js/eosjs.js') }}"></script>
<script>
var election = null;
var auditBallot = null;
function loadElection() {
$.ajax({ url: $("#audit_election_url").val(), dataType: "text" })
.done(function(data) {
try {
election = eosjs.eos.core.objects.__all__.EosObject.deserialise_and_unwrap(eosjs.eos.core.objects.__all__.EosObject.from_json(data), null);
} catch (err) {
loadError(err);
throw err;
}
electionLoaded();
})
.fail(function(xhr, status, err) {
loadError(err);
throw err;
});
}
function electionLoaded() {
$("#audit_error_load").removeClass("visible");
// Audit ballot
var auditResult = document.querySelector("#audit_result");
auditResult.className = "ui message";
var auditResultInner = document.querySelector("#audit_result_inner");
auditResultInner.innerHTML = '<p>Auditing your ballot… Please wait.</p>';
try {
var result = doAuditBallot(auditResult, auditResultInner);
if (result) {
auditResult.className = "ui success message";
} else {
auditResultInner.innerHTML += '<p><i class="remove icon"></i> <b>The ballot has not been correctly prepared.</b></p>';
auditResult.className = "ui error message";
}
} catch (err) {
auditResultInner.innerHTML += '<p><i class="remove icon"></i> Unknown error: ' + err + '</p>';
auditResultInner.innerHTML += '<p><i class="remove icon"></i> <b>The ballot has not been correctly prepared.</b></p>';
auditResult.className = "ui error message";
throw err;
} finally {
document.querySelector("#audit_not_yet_cast").className = "ui negative message";
}
}
function doAuditBallot(auditResult, auditResultInner) {
auditBallot = eosjs.eos.core.objects.__all__.EosObject.deserialise_and_unwrap(eosjs.eos.core.objects.__all__.EosObject.from_json($("#audit_ballot").val()));
//auditResultInner.innerHTML += '<p><i class="checkmark icon"></i> Data is in ballot format.</p>';
if (!eosjs.isinstance(auditBallot, eosjs.eos.base.election.__all__.Ballot)) {
auditResultInner.innerHTML += '<p><i class="remove icon"></i> Error: The data is not in ballot format.</p>';
return false;
}
if (!auditBallot.answers || !auditBallot.answers.__len__ || auditBallot.answers.__len__() === 0) {
auditResultInner.innerHTML += '<p><i class="remove icon"></i> Error: The data is not in audit ballot format.</p>';
return false;
}
auditResultInner.innerHTML += '<p><i class="checkmark icon"></i> The data is in audit ballot format.</p>';
if (auditBallot.election_id !== election._id) {
auditResultInner.innerHTML += '<p><i class="remove icon"></i> Error: The ballot corresponds to a different election.</p>';
return false;
}
auditResultInner.innerHTML += '<p><i class="checkmark icon"></i> The ballot election ID is correct.</p>';
if (auditBallot.election_hash !== eosjs.eos.core.hashing.__all__.SHA256().update_obj(election).hash_as_b64()) {
auditResultInner.innerHTML += '<p><i class="remove icon"></i> Error: The ballot corresponds to a different election.</p>';
return false;
}
auditResultInner.innerHTML += '<p><i class="checkmark icon"></i> The ballot election hash is correct.</p>';
for (var questionNum = 0; questionNum < auditBallot.encrypted_answers.__len__(); questionNum++) {
auditResultInner.innerHTML += '<p><i class="info circle icon"></i> Question number ' + (questionNum + 1) + ':</p>';
// Compute expected plaintexts
var pt = eosjs.eos.core.objects.__all__.EosObject.to_json(eosjs.eos.core.objects.__all__.EosObject.serialise_and_wrap(auditBallot.answers.__getitem__(questionNum)));
var bs = eosjs.eos.psr.bitstream.__all__.BitStream();
bs.write_string(pt);
bs.multiple_of(election.public_key.nbits(), true);
var messages = [];
function callback(val) {
messages.push(val);
}
bs.map(callback, election.public_key.nbits());
var encryptedAnswer = auditBallot.encrypted_answers.__getitem__(questionNum);
for (var blockNum = 0; blockNum < encryptedAnswer.blocks.__len__(); blockNum++) {
var block = encryptedAnswer.blocks.__getitem__(blockNum);
// TODO: Implement this in Python
if (!block.randomness) {
auditResultInner.innerHTML += '<p><i class="remove icon"></i> Error: Block ' + blockNum + ' ciphertext is not a valid audit ciphertext.</p>';
return false;
}
if (!block.is_signature_valid()) {
auditResultInner.innerHTML += '<p><i class="remove icon"></i> Error: Block ' + blockNum + ' signature is not valid.</p>';
return false;
}
if (block.randomness.__lt__(eosjs.eos.core.bigint.__all__.ONE) || block.randomness.__gt__(election.public_key.group.p.__sub__(eosjs.eos.core.bigint.__all__.TWO))) {
auditResultInner.innerHTML += '<p><i class="remove icon"></i> Error: Block ' + blockNum + ' randomness is not valid.</p>';
return false;
}
if (!block.is_randomness_valid()) {
auditResultInner.innerHTML += '<p><i class="remove icon"></i> Error: Block ' + blockNum + ' randomness does not match ciphertext.</p>';
return false;
}
if (!block.m0.__eq__(election.public_key.message_to_m0(messages[blockNum]))) {
auditResultInner.innerHTML += '<p><i class="remove icon"></i> Error: Block ' + blockNum + ' plaintext does not match claimed plaintext.</p>';
return false;
}
}
auditResultInner.innerHTML += '<p><i class="checkmark icon"></i> Question number ' + (questionNum + 1) + ' passed validation.</p>';
}
// Passed validation
var auditContents = document.querySelector("#audit_contents");
auditContents.className = "ui warning message";
var auditContentsInner = document.querySelector("#audit_contents_inner");
auditContentsInner.innerHTML = '';
auditContentsInner.innerHTML += '<p><i class="info circle icon"></i> <b>Please check that the following details match your intended selections:</b></p>';
for (var questionNum = 0; questionNum < auditBallot.encrypted_answers.__len__(); questionNum++) {
auditContentsInner.innerHTML += '<p><i class="icon"></i> Question ' + (questionNum + 1) + ': ' + election.questions.__getitem__(questionNum).pretty_answer(auditBallot.answers.__getitem__(questionNum)) + '</p>';
}
auditContentsInner.innerHTML += '<p><i class="info circle icon"></i> <b>Please check that the ballot fingerprint you recorded matches the following computed ballot fingerprint: <span class="hash">' + eosjs.eos.core.hashing.__all__.SHA256().update_obj(auditBallot).hash_as_b64(true) + '</span>.</b></p>';
auditContentsInner.innerHTML += '<p><i class="checkmark icon"></i> If the selections are correct, and the ballot fingerprint matches, then the ballot has been prepared correctly.</p>';
return true;
}
function loadError(err) {
if (err) {
$("#audit_error_load .techdetails").text("Technical details: " + err);
$("#audit_error_load .techdetails").show();
} else {
$("#audit_error_load .techdetails").hide();
}
$("#audit_error_load").addClass("visible");
}
if (location.search.indexOf("electionUrl=") >= 0) {
var electionUrl = (location.search.split('electionUrl=')[1]||'').split('&')[0];
$("#audit_election_url").val(electionUrl);
}
</script>
{% endblock %}