diff --git a/eos/core/hashing/__init__.py b/eos/core/hashing/__init__.py index 4bd0da7..ad33d39 100644 --- a/eos/core/hashing/__init__.py +++ b/eos/core/hashing/__init__.py @@ -23,6 +23,7 @@ except: pass from eos.core.bigint import * +from eos.core.objects import * # Libraries # ========= @@ -68,6 +69,11 @@ class SHA256: self.update_text(str(value)) return self + def update_obj(self, *values): + for value in values: + self.update_text(EosObject.to_json(EosObject.serialise_and_wrap(value))) + return self + def hash_as_b64(self): if is_python: return base64.b64encode(self.impl.digest()).decode('utf-8') diff --git a/eos/core/objects/__init__.py b/eos/core/objects/__init__.py index bf0511d..bd5736d 100644 --- a/eos/core/objects/__init__.py +++ b/eos/core/objects/__init__.py @@ -151,10 +151,6 @@ class EosObject(metaclass=EosObjectType): return self._instance[0].recurse_parents(cls) return None - @property - def hash(self): - return SHA256().update_text(EosObject.to_json(EosObject.serialise_and_wrap(self))).hash_as_b64() - @staticmethod def serialise_and_wrap(value, object_type=None): if object_type: diff --git a/eos/psr/mixnet.py b/eos/psr/mixnet.py index 1d3c0a9..d81e76d 100644 --- a/eos/psr/mixnet.py +++ b/eos/psr/mixnet.py @@ -63,16 +63,16 @@ class RPCMixnet: if self.is_left: for i in range(len(permutations_and_reenc)): val = permutations_and_reenc[i] - val_obj = MixChallengeResponse(index=val[0], reenc=val[1], rand=val[2]) - commitments.append(SHA256().update_text(EosObject.to_json(val_obj.serialise())).hash_as_bigint()) + val_obj = MixChallengeResponse(challenge_index=i, response_index=val[0], reenc=val[1], rand=val[2]) + commitments.append(SHA256().update_obj(val_obj).hash_as_bigint()) else: for i in range(len(permutations_and_reenc)): # Find the answer that went to 'i' idx = next(idx for idx in range(len(permutations_and_reenc)) if permutations_and_reenc[idx][0] == i) val = permutations_and_reenc[idx] - val_obj = MixChallengeResponse(index=idx, reenc=val[1], rand=val[3]) - commitments.append(SHA256().update_text(EosObject.to_json(val_obj.serialise())).hash_as_bigint()) + val_obj = MixChallengeResponse(challenge_index=i, response_index=idx, reenc=val[1], rand=val[3]) + commitments.append(SHA256().update_obj(val_obj).hash_as_bigint()) self.params = permutations_and_reenc return shuffled_answers, commitments @@ -80,8 +80,8 @@ class RPCMixnet: def challenge(self, i): if self.is_left: val = self.params[i] - return [val[0], val[1], val[2]] + return MixChallengeResponse(challenge_index=i, response_index=val[0], reenc=val[1], rand=val[2]) else: idx = next(idx for idx in range(len(self.params)) if self.params[idx][0] == i) val = self.params[idx] - return [idx, val[1], val[3]] + return MixChallengeResponse(challenge_index=i, response_index=idx, reenc=val[1], rand=val[3]) diff --git a/eos/psr/tests.py b/eos/psr/tests.py index 80c950b..7d0a551 100644 --- a/eos/psr/tests.py +++ b/eos/psr/tests.py @@ -157,14 +157,13 @@ class MixnetTestCase(EosTestCase): self.assertEqual(claimed_blocks[j].delta, reencrypted_block.delta) for i in range(len(pts)): - perm, reencs, rand = mixnet.challenge(i) - val_obj = MixChallengeResponse(index=perm, reenc=reencs, rand=rand) - self.assertEqual(commitments[i], SHA256().update_text(EosObject.to_json(val_obj.serialise())).hash_as_bigint()) + val_obj = mixnet.challenge(i) + self.assertEqual(commitments[i], SHA256().update_obj(val_obj).hash_as_bigint()) if mixnet.is_left: - verify_shuffle(i, perm, reencs) + verify_shuffle(val_obj.challenge_index, val_obj.response_index, val_obj.reenc) else: - verify_shuffle(perm, i, reencs) + verify_shuffle(val_obj.response_index, val_obj.challenge_index, val_obj.reenc) # NB: This isn't doing it in sequence, it's just testing a left mixnet and a right mixnet respectively do_mixnet(0) @@ -258,10 +257,25 @@ class ElectionTestCase(EosTestCase): election.workflow.get_task('eos.psr.workflow.TaskMixVotes').exit() election.save() - # Verify mixes - election.workflow.get_task('eos.psr.workflow.TaskVerifyMixes').enter() + # Prove mixes + election.workflow.get_task('eos.psr.workflow.TaskProveMixes').enter() election.save() + def verify_shuffle(i, j, idx_left, idx_right, reencs): + if j > 0: + orig_answers = election.mixing_trustees[j - 1].mixed_questions[i] + else: + orig_answers = [] + for voter in election.voters: + for ballot in voter.ballots: + orig_answers.append(ballot.encrypted_answers[i]) + + claimed_blocks = election.mixing_trustees[j].mixed_questions[i][idx_right].blocks + for k in range(len(orig_answers[idx_left].blocks)): + reencrypted_block, _ = orig_answers[idx_left].blocks[k].reencrypt(reencs[k]) + self.assertEqual(claimed_blocks[k].gamma, reencrypted_block.gamma) + self.assertEqual(claimed_blocks[k].delta, reencrypted_block.delta) + # Record challenge responses for i in range(len(election.questions)): for j in range(len(election.mixing_trustees)): @@ -280,14 +294,17 @@ class ElectionTestCase(EosTestCase): should_reveal = ((j % 2) == (challenge_bit % 2)) if should_reveal: response = trustee.mixnets[i].challenge(k) - trustee.response[i].append(MixChallengeResponse( - challenge_index=k, - response_index=response[0], - reenc=response[1], - rand=response[2] - )) + trustee.response[i].append(response) + + # Verify proof + self.assertEqual(trustee.commitments[i][k], SHA256().update_obj(response).hash_as_bigint()) + + if j % 2 == 0: + verify_shuffle(i, j, response.challenge_index, response.response_index, response.reenc) + else: + verify_shuffle(i, j, response.response_index, response.challenge_index, response.reenc) - election.workflow.get_task('eos.psr.workflow.TaskVerifyMixes').exit() + election.workflow.get_task('eos.psr.workflow.TaskProveMixes').exit() election.save() # Decrypt votes, for realsies @@ -295,7 +312,7 @@ class ElectionTestCase(EosTestCase): election.save() # Check result - RESULTS = [[[0], [0, 1], [2]], [[0], [1], [0]]] + RESULTS = [[voter[i] for voter in VOTES] for i in range(len(election.questions))] for i in range(len(RESULTS)): votes1 = RESULTS[i] votes2 = [x.choices for x in election.results[i].answers] diff --git a/eos/psr/workflow.py b/eos/psr/workflow.py index 83a09f4..2d6bd37 100644 --- a/eos/psr/workflow.py +++ b/eos/psr/workflow.py @@ -28,7 +28,7 @@ class TaskMixVotes(WorkflowTask): # Do not automatically exit this task pass -class TaskVerifyMixes(WorkflowTask): +class TaskProveMixes(WorkflowTask): depends_on = ['eos.psr.workflow.TaskMixVotes'] def on_enter(self): @@ -36,7 +36,7 @@ class TaskVerifyMixes(WorkflowTask): pass class TaskDecryptVotes(eos.base.workflow.TaskDecryptVotes): - depends_on = ['eos.psr.workflow.TaskVerifyMixes'] + depends_on = ['eos.psr.workflow.TaskProveMixes'] def on_enter(self): election = self.recurse_parents('eos.base.election.Election') @@ -62,6 +62,6 @@ class PSRWorkflow(Workflow): self.tasks.append(TaskOpenVoting()) self.tasks.append(TaskCloseVoting()) self.tasks.append(TaskMixVotes()) - self.tasks.append(TaskVerifyMixes()) + self.tasks.append(TaskProveMixes()) self.tasks.append(TaskDecryptVotes()) # The PSR one, not the base one self.tasks.append(TaskReleaseResults())