diff --git a/testdata/8.tar b/testdata/8.tar index ae8f9d8..79f78dc 100644 Binary files a/testdata/8.tar and b/testdata/8.tar differ diff --git a/testdata/8_vote_weight.tar b/testdata/8_vote_weight.tar new file mode 100644 index 0000000..aaadafc Binary files /dev/null and b/testdata/8_vote_weight.tar differ diff --git a/testdata/8_wrong_ballots.tar b/testdata/8_wrong_ballots.tar index a4986fd..34ca238 100644 Binary files a/testdata/8_wrong_ballots.tar and b/testdata/8_wrong_ballots.tar differ diff --git a/testdata/8_wrong_proof_of_shuffle.tar b/testdata/8_wrong_proof_of_shuffle.tar index 331c2d9..93469fd 100644 Binary files a/testdata/8_wrong_proof_of_shuffle.tar and b/testdata/8_wrong_proof_of_shuffle.tar differ diff --git a/testdata/8_wrong_results.tar b/testdata/8_wrong_results.tar index e13987c..edb51e4 100644 Binary files a/testdata/8_wrong_results.tar and b/testdata/8_wrong_results.tar differ diff --git a/tests.py b/tests.py index 861113b..1080c82 100644 --- a/tests.py +++ b/tests.py @@ -18,7 +18,7 @@ import unittest import subprocess -def run_command(command, return_code=None, **kwargs): +def run_command(command, return_code=None, stdout_contains_text=None, **kwargs): ''' Utility to run a command. timeout is in seconds. @@ -28,12 +28,21 @@ def run_command(command, return_code=None, **kwargs): process = subprocess.run( command, check=True, + stdout=subprocess.PIPE, **kwargs ) return process except subprocess.CalledProcessError as error: if return_code == None or error.returncode != return_code: raise error + finally: + if stdout_contains_text: + if not process.stdout: + raise Exception(f"`{stdout_contains_text}` not in stdout") + process_stdout = process.stdout.decode('utf-8') + for check_text in stdout_contains_text: + if check_text not in process_stdout: + raise Exception(f"`{check_text}` not in process_stdout") class TestStringMethods(unittest.TestCase): def test_election_8(self): @@ -128,6 +137,20 @@ def test_election_8_existing_ballot(self): "./testdata/8.tar", "ae38e56fd663c142387ad9f69d710e9afd1e8c28da3f0ba93facdaae65d273e6" ], + stdout_contains_text=["weight=1"] + ) + + def test_election_8_existing_ballot_vote_weight(self): + ''' + The ballot is located in the ballots.json file. + ''' + run_command( + command=[ + "./election-verifier", + "./testdata/8_vote_weight.tar", + "ae38e56fd663c142387ad9f69d710e9afd1e8c28da3f0ba93facdaae65d273e6" + ], + stdout_contains_text=["weight=2"] ) def test_election_8_nonexisting_ballots(self): diff --git a/verify.py b/verify.py index d850c95..ad2c28d 100644 --- a/verify.py +++ b/verify.py @@ -18,6 +18,7 @@ from tally_methods import tally as tally_methods import sys import os +import csv import signal import hashlib import shutil @@ -141,9 +142,11 @@ def verify_pok_plaintext(pk, proof, ciphertext): # commitment * (alpha) ^ challenge assert first_part == second_part -def verify_votes_pok(pubkeys, dir_path, questions_json, hash): +def verify_votes_pok(pubkeys, dir_path, questions_json, search_hash): num_invalid_votes = 0 linenum = 0 + ballot_found = 0 + found_voter_id = None ciphertexts_path = os.path.join(dir_path, 'ciphertexts_json') with open(ciphertexts_path, mode='r') as votes_file: @@ -164,30 +167,62 @@ def verify_votes_pok(pubkeys, dir_path, questions_json, hash): pubkeys[i]['g'] = int(pubkeys[i]['g']) pubkeys[i]['p'] = int(pubkeys[i]['p']) - found = False - for line in votes_file: - vote = json.loads(line) + votes_reader = csv.reader(votes_file, delimiter="|") + for line in votes_reader: + vote_str, voter_id = line + vote = json.loads(vote_str) linenum += 1 - if linenum % 1000 == 0 and not hash: + if linenum % 1000 == 0 and not search_hash: print_success( "* Verified %d votes (%d invalid).." % ( linenum, num_invalid_votes ) ) - - if ( - hash and - not found and - hash_f(line[:-1].encode('utf-8')).hexdigest() == hash - ): - found = True - print_success( - "* Hash of the vote was successfully found: %s" % hash - ) + + current_hash = hash_f(vote_str.encode('utf-8')).hexdigest() + hash_match = (current_hash == search_hash) + if (search_hash and ballot_found == 0 and hash_match): + ballot_found = 1 + weight_num = None + try: + found_voter_id, weight_str = voter_id.split(".") + + weight_num = int(weight_str) + assert weight_num == ballot_found + except: + print_fail( + f""" + * Hash={search_hash} of the vote found but with invalid + vote_weight={weight_num} (should be {ballot_found}) + """ + ) + sys.exit(1) + elif (search_hash and ballot_found > 0 and hash_match): + ballot_found += 1 + weight_num = None + try: + found_voter_id, weight_str = voter_id.split(".") + weight_num = int(weight_str) + assert weight_num == ballot_found + except: + print_fail( + f""" + * Hash={search_hash} of the vote found but with invalid + vote_weight={weight_num} (should be {ballot_found}) + """ + ) + sys.exit(1) is_invalid = False - if not hash or (hash is not None and found): + if ( + not search_hash or + ( + search_hash is not None and + ballot_found == 1 and + hash_match + ) + ): try: for i in range(num_questions): verify_pok_plaintext( @@ -195,12 +230,10 @@ def verify_votes_pok(pubkeys, dir_path, questions_json, hash): vote['proofs'][i], vote['choices'][i] ) - if hash is not None and found: + if search_hash is not None: print_success("* Verified POK of the found ballot") - return 0, found except SystemExit as e: - break raise e except: is_invalid = True @@ -209,30 +242,38 @@ def verify_votes_pok(pubkeys, dir_path, questions_json, hash): if is_invalid: continue - choice_num = 0 - for f in outvotes_files: - f.write( - json.dumps( - vote['choices'][choice_num], - ensure_ascii=False, - sort_keys=True, - separators=(",", ":") + if not search_hash: + choice_num = 0 + for f in outvotes_files: + f.write( + json.dumps( + vote['choices'][choice_num], + ensure_ascii=False, + sort_keys=True, + separators=(",", ":") + ) ) - ) - f.write("\n") - choice_num += 1 + f.write("\n") + choice_num += 1 for f in outvotes_files: f.close() - if not hash: + if not search_hash: print_success( "* ..finished. Verified %d votes (%d invalid)" % ( linenum, num_invalid_votes ) ) - return num_invalid_votes, found + if ballot_found: + print_success( +f"""* Hash of the vote was successfully found: +\t- hash={hash} +\t- voter_id={found_voter_id} +\t- weight={weight_num}""" + ) + return num_invalid_votes, ballot_found if __name__ == "__main__": @@ -386,13 +427,13 @@ def sig_handler(__signum, __frame): questions_json = json.loads(open(questions_path).read()) print_info("* verifying proofs of knowledge of the plaintexts...") - num_encrypted_invalid_votes, found = verify_votes_pok( + num_encrypted_invalid_votes, found_this_time = verify_votes_pok( pubkeys, dir_raw_path, questions_json, hash ) - hash_found = hash_found or found + hash_found = hash_found or found_this_time print_success( "* proofs of knowledge of plaintexts OK (%d invalid)" % num_encrypted_invalid_votes )