Skip to content

Commit

Permalink
✨ weighted voting support (#59)
Browse files Browse the repository at this point in the history
Parent issue: sequentech/meta#552
  • Loading branch information
Findeton authored Apr 2, 2024
1 parent d3571cf commit b8c4dae
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 35 deletions.
Binary file modified testdata/8.tar
Binary file not shown.
Binary file added testdata/8_vote_weight.tar
Binary file not shown.
Binary file modified testdata/8_wrong_ballots.tar
Binary file not shown.
Binary file modified testdata/8_wrong_proof_of_shuffle.tar
Binary file not shown.
Binary file modified testdata/8_wrong_results.tar
Binary file not shown.
25 changes: 24 additions & 1 deletion tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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):
Expand Down Expand Up @@ -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):
Expand Down
109 changes: 75 additions & 34 deletions verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from tally_methods import tally as tally_methods
import sys
import os
import csv
import signal
import hashlib
import shutil
Expand Down Expand Up @@ -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:
Expand All @@ -164,43 +167,73 @@ 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(
pubkeys[i],
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
Expand All @@ -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__":

Expand Down Expand Up @@ -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
)
Expand Down

0 comments on commit b8c4dae

Please sign in to comment.