def test_publish(self) -> None: # Arrange now = datetime.now(timezone.utc) manifest = Manifest("", ElectionType.unknown, now, now, [], [], [], [], [], []) context = make_ciphertext_election_context( 1, 1, ONE_MOD_P, ONE_MOD_Q, ONE_MOD_Q ) constants = ElectionConstants() devices = [] guardian_records = [GuardianRecord("", "", ONE_MOD_Q, [], [])] encrypted_ballots = [] spoiled_ballots = [] plaintext_tally = PlaintextTally("", []) ciphertext_tally = CiphertextTally("", manifest, context) # Act publish( manifest, context, constants, devices, encrypted_ballots, spoiled_ballots, ciphertext_tally.publish(), plaintext_tally, guardian_records, ) # Assert self.assertTrue(path.exists(RESULTS_DIR)) # Cleanup rmtree(RESULTS_DIR)
def test_publish(self) -> None: # Arrange now = datetime.now(timezone.utc) description = ElectionDescription( "", ElectionType.unknown, now, now, [], [], [], [], [], [] ) context = CiphertextElectionContext(1, 1, ONE_MOD_P, ONE_MOD_Q) constants = ElectionConstants() devices = [] coefficients = [CoefficientValidationSet("", [], [])] encrypted_ballots = [] tally = PlaintextTally("", [], []) # Act publish( description, context, constants, devices, encrypted_ballots, CiphertextTally("", description, context), tally, coefficients, ) # Assert self.assertTrue(path.exists(RESULTS_DIR)) # Cleanup rmtree(RESULTS_DIR)
def start_tally( request: StartTallyRequest = Body(...), scheduler: Scheduler = Depends(get_scheduler), ) -> Any: """ Start a new tally of a collection of ballots """ ballots, description, context = _parse_tally_request(request) tally = CiphertextTally("election-results", description, context) return _tally_ballots(tally, ballots, scheduler)
def _tally_ballots( tally: CiphertextTally, ballots: List[CiphertextAcceptedBallot], scheduler: Scheduler, ) -> Any: """ Append a series of ballots to a new or existing tally """ tally_succeeded = tally.batch_append(ballots, scheduler) if tally_succeeded: published_tally = publish_ciphertext_tally(tally) return published_tally.to_json_object() raise HTTPException(status_code=500, detail="Unable to tally ballots")
def process_message( self, message_type: Literal["tally.cast"], message: Content, context: TrusteeContext, ) -> Tuple[List[Content], ElectionStep[TrusteeContext]]: contests: Dict[CONTEST_ID, CiphertextDecryptionContest] = {} tally_cast: Dict[CONTEST_ID, CiphertextTallyContest] = deserialize( message["content"], Dict[CONTEST_ID, CiphertextTallyContest]) # save for when compensating context.tally = CiphertextTally("election-results", context.election_metadata, context.election_context) context.tally.contests = tally_cast for contest in tally_cast.values(): selections: Dict[ SELECTION_ID, CiphertextDecryptionSelection] = dict( pair_with_object_id( unwrap( compute_decryption_share_for_selection( context.guardian._election_keys, selection, context.election_context, ))) for (_, selection) in contest.selections.items()) contests[contest.object_id] = CiphertextDecryptionContest( contest.object_id, context.guardian_id, contest.description_hash, selections, ) tally_share = DecryptionShare( context.tally.object_id, guardian_id=context.guardian_id, public_key=context.guardian.share_election_public_key().key, contests=contests, ) return [{ "message_type": "tally.trustee_share", "content": serialize(tally_share) }], ProcessEndTally()
def process_message( self, message_type: Literal["vote.cast", "end_vote"], message: Union[Content, dict], context: BulletinBoardContext, ) -> Union[NoReturn, Tuple[List[Content], Optional[ElectionStep]]]: if message_type == "end_vote": context.tally = CiphertextTally("election-results", context.election_metadata, context.election_context) return [], ProcessStartTally() ballot = deserialize(message["content"], CiphertextBallot) if not ballot_is_valid_for_election(ballot, context.election_metadata, context.election_context): raise InvalidBallot() else: return [], None
def _cannot_erroneously_mutate_state( self, tally: CiphertextTally, ballot: SubmittedBallot, state_to_test: BallotBoxState, ) -> bool: input_state = ballot.state ballot.state = state_to_test # remove the first selection first_contest = ballot.contests[0] first_selection = first_contest.ballot_selections[0] ballot.contests[0].ballot_selections.remove(first_selection) self.assertIsNone(tally_ballot(ballot, tally)) self.assertFalse(tally.append(ballot)) # Verify accumulation fails if the selection count does not match if ballot.state == BallotBoxState.CAST: first_tally = tally.contests[first_contest.object_id] self.assertFalse( first_tally.accumulate_contest( ballot.contests[0].ballot_selections)) # pylint: disable=protected-access _key, bad_accumulation = first_tally._accumulate_selections( first_selection.object_id, first_tally.selections[first_selection.object_id], ballot.contests[0].ballot_selections, ) self.assertIsNone(bad_accumulation) ballot.contests[0].ballot_selections.insert(0, first_selection) # modify the contest description hash first_contest_hash = ballot.contests[0].description_hash ballot.contests[0].description_hash = ONE_MOD_Q self.assertIsNone(tally_ballot(ballot, tally)) self.assertFalse(tally.append(ballot)) ballot.contests[0].description_hash = first_contest_hash # modify a contest object id first_contest_object_id = ballot.contests[0].object_id ballot.contests[0].object_id = "a-bad-object-id" self.assertIsNone(tally_ballot(ballot, tally)) self.assertFalse(tally.append(ballot)) ballot.contests[0].object_id = first_contest_object_id # modify a selection object id first_contest_selection_object_id = ( ballot.contests[0].ballot_selections[0].object_id) ballot.contests[0].ballot_selections[ 0].object_id = "another-bad-object-id" self.assertIsNone(tally_ballot(ballot, tally)) self.assertFalse(tally.append(ballot)) # Verify accumulation fails if the selection object id does not match if ballot.state == BallotBoxState.CAST: self.assertFalse(tally.contests[ ballot.contests[0].object_id].accumulate_contest( ballot.contests[0].ballot_selections)) ballot.contests[0].ballot_selections[ 0].object_id = first_contest_selection_object_id # modify the ballot's hash first_ballot_hash = ballot.manifest_hash ballot.manifest_hash = ONE_MOD_Q self.assertIsNone(tally_ballot(ballot, tally)) self.assertFalse(tally.append(ballot)) ballot.manifest_hash = first_ballot_hash ballot.state = input_state return True
def test_tally_ballot_invalid_input_fails( self, everything: ELECTIONS_AND_BALLOTS_TUPLE_TYPE): # Arrange ( _election_description, internal_manifest, ballots, _secret_key, context, ) = everything # encrypt each ballot store = DataStore() encryption_seed = ElectionFactory.get_encryption_device().get_hash() for ballot in ballots: encrypted_ballot = encrypt_ballot(ballot, internal_manifest, context, encryption_seed) encryption_seed = encrypted_ballot.code self.assertIsNotNone(encrypted_ballot) # add to the ballot store store.set( encrypted_ballot.object_id, from_ciphertext_ballot(encrypted_ballot, BallotBoxState.CAST), ) tally = CiphertextTally("my-tally", internal_manifest, context) # act cached_ballots = store.all() first_ballot = cached_ballots[0] first_ballot.state = BallotBoxState.UNKNOWN # verify an UNKNOWN state ballot fails self.assertIsNone(tally_ballot(first_ballot, tally)) self.assertFalse(tally.append(first_ballot)) # cast a ballot first_ballot.state = BallotBoxState.CAST self.assertTrue(tally.append(first_ballot)) # try to append a spoiled ballot first_ballot.state = BallotBoxState.SPOILED self.assertFalse(tally.append(first_ballot)) # Verify accumulation fails if the selection collection is empty if first_ballot.state == BallotBoxState.CAST: self.assertFalse( tally.contests[first_ballot.object_id].accumulate_contest([])) # pylint: disable=protected-access # pop the cast ballot tally._cast_ballot_ids.pop() # reset to cast first_ballot.state = BallotBoxState.CAST self.assertTrue( self._cannot_erroneously_mutate_state(tally, first_ballot, BallotBoxState.CAST)) self.assertTrue( self._cannot_erroneously_mutate_state(tally, first_ballot, BallotBoxState.SPOILED)) self.assertTrue( self._cannot_erroneously_mutate_state(tally, first_ballot, BallotBoxState.UNKNOWN)) # verify a cast ballot cannot be added twice first_ballot.state = BallotBoxState.CAST self.assertTrue(tally.append(first_ballot)) self.assertFalse(tally.append(first_ballot)) # verify an already submitted ballot cannot be changed or readded first_ballot.state = BallotBoxState.SPOILED self.assertFalse(tally.append(first_ballot))
def test_tally_ballot_invalid_input_fails( self, everything: ELECTIONS_AND_BALLOTS_TUPLE_TYPE): # Arrange metadata, ballots, secret_key, context = everything # encrypt each ballot store = BallotStore() seed_hash = EncryptionDevice("Location").get_hash() for ballot in ballots: encrypted_ballot = encrypt_ballot(ballot, metadata, context, seed_hash) seed_hash = encrypted_ballot.tracking_hash self.assertIsNotNone(encrypted_ballot) # add to the ballot store store.set( encrypted_ballot.object_id, from_ciphertext_ballot(encrypted_ballot, BallotBoxState.CAST), ) subject = CiphertextTally("my-tally", metadata, context) # act cached_ballots = store.all() first_ballot = cached_ballots[0] first_ballot.state = BallotBoxState.UNKNOWN # verify an UNKNOWN state ballot fails self.assertIsNone(tally_ballot(first_ballot, subject)) self.assertFalse(subject.append(first_ballot)) # cast a ballot first_ballot.state = BallotBoxState.CAST self.assertTrue(subject.append(first_ballot)) # try to append a spoiled ballot first_ballot.state = BallotBoxState.SPOILED self.assertFalse(subject.append(first_ballot)) # Verify accumulation fails if the selection collection is empty if first_ballot.state == BallotBoxState.CAST: self.assertFalse( subject.cast[first_ballot.object_id].elgamal_accumulate([])) # pop the cast ballot subject._cast_ballot_ids.pop() # reset to cast first_ballot.state = BallotBoxState.CAST self.assertTrue( self._cannot_erroneously_mutate_state(subject, first_ballot, BallotBoxState.CAST)) self.assertTrue( self._cannot_erroneously_mutate_state(subject, first_ballot, BallotBoxState.SPOILED)) self.assertTrue( self._cannot_erroneously_mutate_state(subject, first_ballot, BallotBoxState.UNKNOWN)) # verify a spoiled ballot cannot be added twice first_ballot.state = BallotBoxState.SPOILED self.assertTrue(subject.append(first_ballot)) self.assertFalse(subject.append(first_ballot)) # verify an already spoiled ballot cannot be cast first_ballot.state = BallotBoxState.CAST self.assertFalse(subject.append(first_ballot)) # pop the spoiled ballot subject.spoiled_ballots.pop(first_ballot.object_id) # verify a cast ballot cannot be added twice first_ballot.state = BallotBoxState.CAST self.assertTrue(subject.append(first_ballot)) self.assertFalse(subject.append(first_ballot)) # verify an already cast ballot cannot be spoiled first_ballot.state = BallotBoxState.SPOILED self.assertFalse(subject.append(first_ballot))
def close_ballot_box(self): self.context.tally = CiphertextTally('election-results', self.context.election_metadata, self.context.election_context) self.step = ProcessTrusteeShare()