Пример #1
0
    def test_tracker_hash_rotates(self):
        # Arrange
        device = EncryptionDevice("Location")
        ballot_hash_1 = ONE_MOD_Q
        ballot_hash_2 = TWO_MOD_Q
        timestamp_1 = 1000
        timestamp_2 = 2000

        # Act
        device_hash = get_hash_for_device(device.uuid, device.location)
        tracker_1_hash = get_rotating_tracker_hash(
            device_hash, timestamp_1, ballot_hash_1
        )
        tracker_2_hash = get_rotating_tracker_hash(
            device_hash, timestamp_2, ballot_hash_2
        )

        # Assert
        self.assertIsNotNone(device_hash)
        self.assertIsNotNone(tracker_1_hash)
        self.assertIsNotNone(tracker_2_hash)

        self.assertNotEqual(device_hash, ZERO_MOD_Q)
        self.assertNotEqual(tracker_1_hash, device_hash)
        self.assertNotEqual(tracker_2_hash, device_hash)
        self.assertNotEqual(tracker_1_hash, tracker_2_hash)
Пример #2
0
    def step_2_encrypt_votes(self) -> None:
        """
        Using the `CiphertextElectionContext` encrypt ballots for the election
        """

        # Configure the Encryption Device
        self.device = EncryptionDevice("polling-place-one")
        self.encrypter = EncryptionMediator(self.metadata, self.context,
                                            self.device)
        self._assert_message(
            EncryptionDevice.__qualname__,
            f"Ready to encrypt at location: {self.device.location}",
        )

        # Load some Ballots
        self.plaintext_ballots = BallotFactory().get_simple_ballots_from_file()
        self._assert_message(
            PlaintextBallot.__qualname__,
            f"Loaded ballots: {len(self.plaintext_ballots)}",
            len(self.plaintext_ballots) > 0,
        )

        # Encrypt the Ballots
        for plaintext_ballot in self.plaintext_ballots:
            encrypted_ballot = self.encrypter.encrypt(plaintext_ballot)
            self._assert_message(
                EncryptionMediator.encrypt.__qualname__,
                f"Ballot Id: {plaintext_ballot.object_id}",
                encrypted_ballot is not None,
            )
            self.ciphertext_ballots.append(get_optional(encrypted_ballot))
Пример #3
0
 def get_encryption_device() -> EncryptionDevice:
     return EncryptionDevice(
         generate_device_uuid(),
         "Session",
         12345,
         f"polling-place-{str(uuid.uuid1())}",
     )
    def test_decrypt_ballot_valid_input_missing_nonce_fails(
        self, keypair: ElGamalKeyPair
    ):

        # Arrange
        election = election_factory.get_simple_election_from_file()
        metadata, context = election_factory.get_fake_ciphertext_election(
            election, keypair.public_key
        )

        data = ballot_factory.get_simple_ballot_from_file()
        device = EncryptionDevice("Location")
        operator = EncryptionMediator(metadata, context, device)

        # Act
        subject = operator.encrypt(data)
        self.assertIsNotNone(subject)
        subject.nonce = None

        missing_nonce_value = None

        result_from_nonce = decrypt_ballot_with_nonce(
            subject, metadata, context.crypto_extended_base_hash, keypair.public_key,
        )
        result_from_nonce_seed = decrypt_ballot_with_nonce(
            subject,
            metadata,
            context.crypto_extended_base_hash,
            keypair.public_key,
            missing_nonce_value,
        )

        # Assert
        self.assertIsNone(result_from_nonce)
        self.assertIsNone(result_from_nonce_seed)
Пример #5
0
    def test_eg_conversion(self, state: DominionBallotsAndContext,
                           seed: ElementModQ) -> None:
        ied = InternalElectionDescription(state.ed)
        ballot_box = BallotBox(ied, state.cec)

        seed_hash = EncryptionDevice("Location").get_hash()
        nonces = Nonces(seed)[0:len(state.ballots)]

        for b, n in zip(state.ballots, nonces):
            eb = encrypt_ballot(b, ied, state.cec, seed_hash, n)
            self.assertIsNotNone(eb)

            pb = decrypt_ballot_with_secret(
                eb,
                ied,
                state.cec.crypto_extended_base_hash,
                state.cec.elgamal_public_key,
                state.secret_key,
            )
            self.assertEqual(b, pb)

            self.assertGreater(len(eb.contests), 0)
            cast_result = ballot_box.cast(eb)
            self.assertIsNotNone(cast_result)

        tally = tally_ballots(ballot_box._store, ied, state.cec)
        self.assertIsNotNone(tally)
        results = decrypt_tally_with_secret(tally, state.secret_key)

        self.assertEqual(len(results.keys()), len(state.id_map.keys()))
        for obj_id in results.keys():
            self.assertIn(obj_id, state.id_map)
            cvr_sum = int(state.dominion_cvrs.data[state.id_map[obj_id]].sum())
            decryption = results[obj_id]
            self.assertEqual(cvr_sum, decryption)
Пример #6
0
    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
            )
Пример #7
0
    def test_tally_spoiled_ballots_accumulates_valid_tally(
            self, everything: ELECTIONS_AND_BALLOTS_TUPLE_TYPE):
        # Arrange
        metadata, ballots, secret_key, context = everything
        # Tally the plaintext ballots for comparison later
        plaintext_tallies = accumulate_plaintext_ballots(ballots)

        # 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.SPOILED),
            )

        # act
        result = tally_ballots(store, metadata, context)
        self.assertIsNotNone(result)

        # Assert
        decrypted_tallies = self._decrypt_with_secret(result, secret_key)
        self.assertCountEqual(plaintext_tallies, decrypted_tallies)
        for value in decrypted_tallies.values():
            self.assertEqual(0, value)
        self.assertEqual(len(ballots), len(result.spoiled_ballots))
Пример #8
0
    def test_encrypt_simple_ballot_from_files_succeeds(self):
        # Arrange
        keypair = elgamal_keypair_from_secret(int_to_q(2))
        election = election_factory.get_simple_election_from_file()
        metadata, context = election_factory.get_fake_ciphertext_election(
            election, keypair.public_key
        )

        data = ballot_factory.get_simple_ballot_from_file()
        self.assertTrue(data.is_valid(metadata.ballot_styles[0].object_id))

        device = EncryptionDevice("Location")
        subject = EncryptionMediator(metadata, context, device)

        # Act
        result = subject.encrypt(data)

        # Assert
        self.assertIsNotNone(result)
        self.assertEqual(data.object_id, result.object_id)
        self.assertTrue(
            result.is_valid_encryption(
                metadata.description_hash,
                keypair.public_key,
                context.crypto_extended_base_hash,
            )
        )
    def verify_results(self) -> None:
        """Verify results of election"""

        # Deserialize
        manifest_from_file = Manifest.from_json_file(MANIFEST_FILE_NAME,
                                                     RESULTS_DIR)
        self.assertEqual(self.manifest, manifest_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)

        for ballot in self.ballot_store.all():
            name = BALLOT_PREFIX + ballot.object_id
            ballot_from_file = SubmittedBallot.from_json_file(
                name, BALLOTS_DIR)
            self.assertEqual(ballot, ballot_from_file)

        for spoiled_ballot in self.plaintext_spoiled_ballots.values():
            name = BALLOT_PREFIX + spoiled_ballot.object_id
            spoiled_ballot_from_file = PlaintextTally.from_json_file(
                name, SPOILED_DIR)
            self.assertEqual(spoiled_ballot, spoiled_ballot_from_file)

        published_ciphertext_tally_from_file = PublishedCiphertextTally.from_json_file(
            ENCRYPTED_TALLY_FILE_NAME, RESULTS_DIR)
        self.assertEqual(self.ciphertext_tally.publish(),
                         published_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)

        for guardian_record in self.guardian_records:
            set_name = COEFFICIENT_PREFIX + guardian_record.guardian_id
            guardian_record_from_file = GuardianRecord.from_json_file(
                set_name, GUARDIAN_DIR)
            self.assertEqual(guardian_record, guardian_record_from_file)
Пример #10
0
def create() -> Tuple:
    """
    An election with only one guardian and random keys gets generated.
    More configuration options and the ability to hold a key ceremony should be added later.
    """
    # Open an election manifest file
    with open(os.path.join(ELECTION_MANIFEST), "r") as manifest:
        string_representation = manifest.read()
        election_description = ElectionDescription.from_json(
            string_representation)

    # Create an election builder instance, and configure it for a single public-private keypair.
    # in a real election, you would configure this for a group of guardians.  See Key Ceremony for more information.
    # TODO: Allow real key ceremony
    builder = ElectionBuilder(
        number_of_guardians=
        1,  # since we will generate a single public-private keypair, we set this to 1
        quorum=
        1,  # since we will generate a single public-private keypair, we set this to 1
        description=election_description)

    # We simply generate a random keypair. For a real election this step should
    # be replaced by the key ceremony
    keypair = elgamal_keypair_random()

    builder.set_public_key(keypair.public_key)

    # get an `InternalElectionDescription` and `CiphertextElectionContext`
    # that are used for the remainder of the election.
    (metadata, context) = builder.build()

    # Configure an encryption device
    # In the docs the encrypter device gets defined when encrypting a ballot.
    # I think for our usecase it makes more sense to define one encrypter and use for the whole election
    device = EncryptionDevice("polling-place-one")
    encrypter = EncryptionMediator(metadata, context, device)

    store = BallotStore()
    ballot_box = BallotBox(metadata, context, store)

    return metadata, context, encrypter, ballot_box, store, keypair
Пример #11
0
    def test_tracker_converts_to_words(self):
        # Arrange
        device = EncryptionDevice("Location")
        device_hash = get_hash_for_device(device.uuid, device.location)
        ballot_hash = ONE_MOD_Q
        ballot_hash_different = TWO_MOD_Q
        timestamp = 1000
        tracker_hash = get_rotating_tracker_hash(device_hash, timestamp,
                                                 ballot_hash)
        tracker_hash_different = get_rotating_tracker_hash(
            device_hash, timestamp, ballot_hash_different)

        # Act
        device_words = tracker_hash_to_words(device_hash)
        tracker_words = tracker_hash_to_words(tracker_hash)
        tracker_different_words = tracker_hash_to_words(tracker_hash_different)

        # Assert
        self.assertIsNotNone(device_words)
        self.assertIsNotNone(tracker_words)
        self.assertNotEqual(device_words, tracker_words)
        self.assertNotEqual(tracker_different_words, tracker_words)
    def setUp(self):

        self.key_ceremony = KeyCeremonyMediator(self.CEREMONY_DETAILS)

        self.guardians: List[Guardian] = []

        # Setup Guardians
        for i in range(self.NUMBER_OF_GUARDIANS):
            sequence = i + 2
            self.guardians.append(
                Guardian(
                    "guardian_" + str(sequence),
                    sequence,
                    self.NUMBER_OF_GUARDIANS,
                    self.QUORUM,
                ))

        # Attendance (Public Key Share)
        for guardian in self.guardians:
            self.key_ceremony.announce(guardian)

        self.key_ceremony.orchestrate(identity_auxiliary_encrypt)
        self.key_ceremony.verify(identity_auxiliary_decrypt)

        self.joint_public_key = self.key_ceremony.publish_joint_key()
        self.assertIsNotNone(self.joint_public_key)

        # setup the election
        self.election = election_factory.get_fake_election()
        builder = ElectionBuilder(self.NUMBER_OF_GUARDIANS, self.QUORUM,
                                  self.election)

        self.assertIsNone(
            builder.build())  # Can't build without the public key

        builder.set_public_key(self.joint_public_key)
        self.metadata, self.context = get_optional(builder.build())

        self.encryption_device = EncryptionDevice("location")
        self.ballot_marking_device = EncryptionMediator(
            self.metadata, self.context, self.encryption_device)

        # get some fake ballots
        self.fake_cast_ballot = ballot_factory.get_fake_ballot(
            self.metadata, "some-unique-ballot-id-cast")
        self.more_fake_ballots = []
        for i in range(10):
            self.more_fake_ballots.append(
                ballot_factory.get_fake_ballot(
                    self.metadata, f"some-unique-ballot-id-cast{i}"))
        self.fake_spoiled_ballot = ballot_factory.get_fake_ballot(
            self.metadata, "some-unique-ballot-id-spoiled")
        self.assertTrue(
            self.fake_cast_ballot.is_valid(
                self.metadata.ballot_styles[0].object_id))
        self.assertTrue(
            self.fake_spoiled_ballot.is_valid(
                self.metadata.ballot_styles[0].object_id))
        self.expected_plaintext_tally = accumulate_plaintext_ballots(
            [self.fake_cast_ballot] + self.more_fake_ballots)

        # Fill in the expected values with any missing selections
        # that were not made on any ballots
        selection_ids = set([
            selection.object_id for contest in self.metadata.contests
            for selection in contest.ballot_selections
        ])

        missing_selection_ids = selection_ids.difference(
            set(self.expected_plaintext_tally))

        for id in missing_selection_ids:
            self.expected_plaintext_tally[id] = 0

        # Encrypt
        encrypted_fake_cast_ballot = self.ballot_marking_device.encrypt(
            self.fake_cast_ballot)
        encrypted_fake_spoiled_ballot = self.ballot_marking_device.encrypt(
            self.fake_spoiled_ballot)
        self.assertIsNotNone(encrypted_fake_cast_ballot)
        self.assertIsNotNone(encrypted_fake_spoiled_ballot)
        self.assertTrue(
            encrypted_fake_cast_ballot.is_valid_encryption(
                self.context.crypto_extended_base_hash, self.joint_public_key))

        # encrypt some more fake ballots
        self.more_fake_encrypted_ballots = []
        for fake_ballot in self.more_fake_ballots:
            self.more_fake_encrypted_ballots.append(
                self.ballot_marking_device.encrypt(fake_ballot))

        # configure the ballot box
        ballot_store = BallotStore()
        ballot_box = BallotBox(self.metadata, self.context, ballot_store)
        ballot_box.cast(encrypted_fake_cast_ballot)
        ballot_box.spoil(encrypted_fake_spoiled_ballot)

        # Cast some more fake ballots
        for fake_ballot in self.more_fake_encrypted_ballots:
            ballot_box.cast(fake_ballot)

        # generate encrypted tally
        self.ciphertext_tally = tally_ballots(ballot_store, self.metadata,
                                              self.context)
from electionguard.ballot_box import (
    BallotBox,
    accept_ballot,
)
from electionguard.ballot_validator import ballot_is_valid_for_election
from electionguard.elgamal import elgamal_keypair_from_secret
from electionguard.encrypt import encrypt_ballot, EncryptionDevice
from electionguard.group import int_to_q

import electionguardtest.ballot_factory as BallotFactory
import electionguardtest.election_factory as ElectionFactory

election_factory = ElectionFactory.ElectionFactory()
ballot_factory = BallotFactory.BallotFactory()

SEED_HASH = EncryptionDevice("Location").get_hash()


class TestBallotBox(TestCase):
    def test_ballot_box_cast_ballot(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
        )
        store = BallotStore()
        source = election_factory.get_fake_ballot(metadata)
        self.assertTrue(source.is_valid(metadata.ballot_styles[0].object_id))

        # Act
    def test_decrypt_ballot_valid_input_succeeds(self, keypair: ElGamalKeyPair):
        """
        Check that decryption works as expected by encrypting a ballot using the stateful `EncryptionMediator`
        and then calling the various decrypt functions.
        """

        # TODO: Hypothesis test instead

        # Arrange
        election = election_factory.get_simple_election_from_file()
        metadata, context = election_factory.get_fake_ciphertext_election(
            election, keypair.public_key
        )

        data = ballot_factory.get_simple_ballot_from_file()
        device = EncryptionDevice("Location")
        operator = EncryptionMediator(metadata, context, device)

        # Act
        subject = operator.encrypt(data)
        self.assertIsNotNone(subject)

        result_from_key = decrypt_ballot_with_secret(
            subject,
            metadata,
            context.crypto_extended_base_hash,
            keypair.public_key,
            keypair.secret_key,
            remove_placeholders=False,
        )
        result_from_nonce = decrypt_ballot_with_nonce(
            subject,
            metadata,
            context.crypto_extended_base_hash,
            keypair.public_key,
            remove_placeholders=False,
        )
        result_from_nonce_seed = decrypt_ballot_with_nonce(
            subject,
            metadata,
            context.crypto_extended_base_hash,
            keypair.public_key,
            subject.nonce,
            remove_placeholders=False,
        )

        # Assert
        self.assertIsNotNone(result_from_key)
        self.assertIsNotNone(result_from_nonce)
        self.assertIsNotNone(result_from_nonce_seed)
        self.assertEqual(data.object_id, subject.object_id)
        self.assertEqual(data.object_id, result_from_key.object_id)
        self.assertEqual(data.object_id, result_from_nonce.object_id)
        self.assertEqual(data.object_id, result_from_nonce_seed.object_id)

        for description in metadata.get_contests_for(data.ballot_style):

            expected_entries = (
                len(description.ballot_selections) + description.number_elected
            )

            key_contest = [
                contest
                for contest in result_from_key.contests
                if contest.object_id == description.object_id
            ][0]
            nonce_contest = [
                contest
                for contest in result_from_nonce.contests
                if contest.object_id == description.object_id
            ][0]
            seed_contest = [
                contest
                for contest in result_from_nonce_seed.contests
                if contest.object_id == description.object_id
            ][0]

            # Contests may not be voted on the ballot
            data_contest_exists = [
                contest
                for contest in data.contests
                if contest.object_id == description.object_id
            ]
            if any(data_contest_exists):
                data_contest = data_contest_exists[0]
            else:
                data_contest = None

            self.assertTrue(
                key_contest.is_valid(
                    description.object_id,
                    expected_entries,
                    description.number_elected,
                    description.votes_allowed,
                )
            )
            self.assertTrue(
                nonce_contest.is_valid(
                    description.object_id,
                    expected_entries,
                    description.number_elected,
                    description.votes_allowed,
                )
            )
            self.assertTrue(
                seed_contest.is_valid(
                    description.object_id,
                    expected_entries,
                    description.number_elected,
                    description.votes_allowed,
                )
            )

            for selection_description in description.ballot_selections:

                key_selection = [
                    selection
                    for selection in key_contest.ballot_selections
                    if selection.object_id == selection_description.object_id
                ][0]
                nonce_selection = [
                    selection
                    for selection in nonce_contest.ballot_selections
                    if selection.object_id == selection_description.object_id
                ][0]
                seed_selection = [
                    selection
                    for selection in seed_contest.ballot_selections
                    if selection.object_id == selection_description.object_id
                ][0]

                # Selections may be undervoted for a specific contest
                if any(data_contest_exists):
                    data_selection_exists = [
                        selection
                        for selection in data_contest.ballot_selections
                        if selection.object_id == selection_description.object_id
                    ]
                else:
                    data_selection_exists = []

                if any(data_selection_exists):
                    data_selection = data_selection_exists[0]
                    self.assertTrue(data_selection.to_int() == key_selection.to_int())
                    self.assertTrue(data_selection.to_int() == nonce_selection.to_int())
                    self.assertTrue(data_selection.to_int() == seed_selection.to_int())
                else:
                    data_selection = None

                # TODO: also check edge cases such as:
                # - placeholder selections are true for under votes

                self.assertTrue(key_selection.is_valid(selection_description.object_id))
                self.assertTrue(
                    nonce_selection.is_valid(selection_description.object_id)
                )
                self.assertTrue(
                    seed_selection.is_valid(selection_description.object_id)
                )
    def test_encrypt_ballot_with_derivative_nonces_regenerates_valid_proofs(
            self, keypair: ElGamalKeyPair):
        """
        This test verifies that we can regenerate the contest and selection proofs from the cached nonce values
        """

        # TODO: Hypothesis test instead

        # Arrange
        election = election_factory.get_simple_election_from_file()
        metadata, context = election_factory.get_fake_ciphertext_election(
            election, keypair.public_key)

        data = ballot_factory.get_simple_ballot_from_file()
        self.assertTrue(data.is_valid(metadata.ballot_styles[0].object_id))

        device = EncryptionDevice("Location")
        subject = EncryptionMediator(metadata, context, device)

        # Act
        result = subject.encrypt(data)
        self.assertTrue(
            result.is_valid_encryption(context.crypto_extended_base_hash,
                                       keypair.public_key))

        # Assert
        for contest in result.contests:
            # Find the contest description
            contest_description = list(
                filter(lambda i: i.object_id == contest.object_id,
                       metadata.contests))[0]

            # Homomorpically accumulate the selection encryptions
            elgamal_accumulation = elgamal_add(
                *
                [selection.message for selection in contest.ballot_selections])
            # accumulate the selection nonce's
            aggregate_nonce = add_q(
                *[selection.nonce for selection in contest.ballot_selections])

            regenerated_constant = make_constant_chaum_pedersen(
                elgamal_accumulation,
                contest_description.number_elected,
                aggregate_nonce,
                keypair.public_key,
                add_q(contest.nonce, TWO_MOD_Q),
            )

            self.assertTrue(
                regenerated_constant.is_valid(elgamal_accumulation,
                                              keypair.public_key))

            for selection in contest.ballot_selections:
                # Since we know the nonce, we can decrypt the plaintext
                representation = selection.message.decrypt_known_nonce(
                    keypair.public_key, selection.nonce)

                # one could also decrypt with the secret key:
                # representation = selection.message.decrypt(keypair.secret_key)

                regenerated_disjuctive = make_disjunctive_chaum_pedersen(
                    selection.message,
                    selection.nonce,
                    keypair.public_key,
                    add_q(selection.nonce, TWO_MOD_Q),
                    representation,
                )

                self.assertTrue(
                    regenerated_disjuctive.is_valid(selection.message,
                                                    keypair.public_key))
 def __init__(self) -> None:
     """Initialize the class"""
     self.election_factory = ElectionFactory()
     self.ballot_factory = BallotFactory()
     self.encryption_device = EncryptionDevice(f"polling-place-{uuid.uuid1}")
Пример #17
0
    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))