def verify_results(self) -> None: """Verify results of election""" # Deserialize description_from_file = ElectionDescription.from_json_file( DESCRIPTION_FILE_NAME, RESULTS_DIR ) self.assertEqual(self.description, description_from_file) context_from_file = CiphertextElectionContext.from_json_file( CONTEXT_FILE_NAME, RESULTS_DIR ) self.assertEqual(self.context, context_from_file) constants_from_file = ElectionConstants.from_json_file( CONSTANTS_FILE_NAME, RESULTS_DIR ) self.assertEqual(self.constants, constants_from_file) device_name = DEVICE_PREFIX + str(self.device.uuid) device_from_file = EncryptionDevice.from_json_file(device_name, DEVICES_DIR) self.assertEqual(self.device, device_from_file) ciphertext_ballots: List[CiphertextAcceptedBallot] = [] for ballot in self.ballot_store.all(): ballot_name = BALLOT_PREFIX + ballot.object_id ballot_from_file = CiphertextAcceptedBallot.from_json_file( ballot_name, BALLOTS_DIR ) self.assertEqual(ballot, ballot_from_file) spoiled_ballots: List[CiphertextAcceptedBallot] = [] for spoiled_ballot in self.ciphertext_tally.spoiled_ballots.values(): ballot_name = BALLOT_PREFIX + spoiled_ballot.object_id spoiled_ballot_from_file = CiphertextAcceptedBallot.from_json_file( ballot_name, SPOILED_DIR ) self.assertEqual(spoiled_ballot, spoiled_ballot_from_file) ciphertext_tally_from_file = PublishedCiphertextTally.from_json_file( ENCRYPTED_TALLY_FILE_NAME, RESULTS_DIR ) self.assertEqual( publish_ciphertext_tally(self.ciphertext_tally), ciphertext_tally_from_file ) plainttext_tally_from_file = PlaintextTally.from_json_file( TALLY_FILE_NAME, RESULTS_DIR ) self.assertEqual(self.plaintext_tally, plainttext_tally_from_file) coefficient_validation_sets: List[CoefficientValidationSet] = [] for coefficient_validation_set in self.coefficient_validation_sets: set_name = COEFFICIENT_PREFIX + coefficient_validation_set.owner_id coefficient_validation_set_from_file = CoefficientValidationSet.from_json_file( set_name, COEFFICIENTS_DIR ) self.assertEqual( coefficient_validation_set, coefficient_validation_set_from_file )
def verify_ballot_proof( cec: CiphertextElectionContext, ballot: CiphertextAcceptedBallot) -> bool: # pragma: no cover """ Given a ballot, verify its Chaum-Pedersen proofs. """ return ballot.is_valid_encryption(ballot.description_hash, cec.elgamal_public_key, cec.crypto_extended_base_hash)
def _parse_tally_request( request: StartTallyRequest, ) -> Tuple[List[CiphertextAcceptedBallot], InternalElectionDescription, CiphertextElectionContext, ]: """ Deserialize common tally request values """ ballots = [ CiphertextAcceptedBallot.from_json_object(ballot) for ballot in request.ballots ] description = ElectionDescription.from_json_object(request.description) internal_description = InternalElectionDescription(description) context = CiphertextElectionContext.from_json_object(request.context) return (ballots, internal_description, context)
def decrypt_ballots(request: DecryptBallotsRequest = Body(...)) -> Any: ballots = [ CiphertextAcceptedBallot.from_json_object(ballot) for ballot in request.encrypted_ballots ] context: CiphertextElectionContext = CiphertextElectionContext.from_json_object( request.context) all_shares: List[BallotDecryptionShare] = [ read_json_object(share, BallotDecryptionShare) for shares in request.shares.values() for share in shares ] shares_by_ballot = index_shares_by_ballot(all_shares) extended_base_hash = context.crypto_extended_base_hash decrypted_ballots = { ballot.object_id: decrypt_ballot(ballot, shares_by_ballot[ballot.object_id], extended_base_hash) for ballot in ballots } return write_json_object(decrypted_ballots)
def decrypt_ballot_shares( request: DecryptBallotSharesRequest = Body(...), scheduler: Scheduler = Depends(get_scheduler), ) -> Any: """ Decrypt this guardian's share of one or more ballots """ ballots = [ CiphertextAcceptedBallot.from_json_object(ballot) for ballot in request.encrypted_ballots ] context = CiphertextElectionContext.from_json_object(request.context) guardian = convert_guardian(request.guardian) shares = [ compute_decryption_share_for_ballot(guardian, ballot, context, scheduler) for ballot in ballots ] response = DecryptBallotSharesResponse( shares=[write_json_object(share) for share in shares] ) return response
def test_ballot_store(self): # Arrange keypair = elgamal_keypair_from_secret(int_to_q(2)) election = election_factory.get_fake_election() metadata, context = election_factory.get_fake_ciphertext_election( election, keypair.public_key) # get an encrypted fake ballot to work with fake_ballot = election_factory.get_fake_ballot(metadata) encrypted_ballot = encrypt_ballot(fake_ballot, metadata, context, SEED_HASH) # Set up the ballot store subject = BallotStore() data_cast = CiphertextAcceptedBallot( encrypted_ballot.object_id, encrypted_ballot.ballot_style, encrypted_ballot.description_hash, encrypted_ballot.previous_tracking_hash, encrypted_ballot.contests, encrypted_ballot.tracking_hash, encrypted_ballot.timestamp, ) data_cast.state = BallotBoxState.CAST data_spoiled = CiphertextAcceptedBallot( encrypted_ballot.object_id, encrypted_ballot.ballot_style, encrypted_ballot.description_hash, encrypted_ballot.previous_tracking_hash, encrypted_ballot.contests, encrypted_ballot.tracking_hash, encrypted_ballot.timestamp, ) data_spoiled.state = BallotBoxState.SPOILED self.assertIsNone(subject.get("cast")) self.assertIsNone(subject.get("spoiled")) # try to set a ballot with an unknown state self.assertFalse( subject.set( "unknown", CiphertextAcceptedBallot( encrypted_ballot.object_id, encrypted_ballot.ballot_style, encrypted_ballot.description_hash, encrypted_ballot.previous_tracking_hash, encrypted_ballot.contests, encrypted_ballot.tracking_hash, encrypted_ballot.timestamp, ), )) # Act self.assertTrue(subject.set("cast", data_cast)) self.assertTrue(subject.set("spoiled", data_spoiled)) self.assertEqual(subject.get("cast"), data_cast) self.assertEqual(subject.get("spoiled"), data_spoiled) self.assertEqual(subject.exists("cast"), (True, data_cast)) self.assertEqual(subject.exists("spoiled"), (True, data_spoiled)) # test mutate state data_cast.state = BallotBoxState.UNKNOWN self.assertEqual(subject.exists("cast"), (False, data_cast)) # test remove self.assertTrue(subject.set("cast", None)) self.assertEqual(subject.exists("cast"), (False, None))
def _cannot_erroneously_mutate_state( self, subject: CiphertextTally, ballot: CiphertextAcceptedBallot, 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, subject)) self.assertFalse(subject.append(ballot)) # Verify accumulation fails if the selection count does not match if ballot.state == BallotBoxState.CAST: first_tally = subject.cast[first_contest.object_id] self.assertFalse( first_tally.elgamal_accumulate( ballot.contests[0].ballot_selections)) key, bad_accumulation = first_tally._accumulate_selections( first_selection.object_id, first_tally.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, subject)) self.assertFalse(subject.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, subject)) self.assertFalse(subject.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, subject)) self.assertFalse(subject.append(ballot)) # Verify accumulation fails if the selection object id does not match if ballot.state == BallotBoxState.CAST: self.assertFalse( subject.cast[ballot.contests[0].object_id].elgamal_accumulate( 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.description_hash ballot.description_hash = ONE_MOD_Q self.assertIsNone(tally_ballot(ballot, subject)) self.assertFalse(subject.append(ballot)) ballot.description_hash = first_ballot_hash ballot.state = input_state return True