{% 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() + '</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 %}