def get_contest_description_well_formed(
    draw: _DrawType,
    ints=integers(1, 20),
    txt=text(),
    email_addresses=emails(),
    selections=get_selection_description_well_formed(),
    sequence_order: Optional[int] = None,
    electoral_district_id: Optional[str] = None,
) -> Tuple[str, ContestDescription]:
    object_id = f"{draw(email_addresses)}-contest"

    if sequence_order is None:
        sequence_order = draw(ints)

    if electoral_district_id is None:
        electoral_district_id = f"{draw(email_addresses)}-gp-unit"

    first_int = draw(ints)
    second_int = draw(ints)

    # TODO ISSUE #33: support more votes than seats for other VoteVariationType options
    number_elected = min(first_int, second_int)
    votes_allowed = number_elected

    selection_descriptions: List[SelectionDescription] = list()
    for i in range(max(first_int, second_int)):
        selection: Tuple[str, SelectionDescription] = draw(selections)
        _, selection_description = selection
        selection_description.sequence_order = i
        selection_descriptions.append(selection_description)

    contest_description = ContestDescription(
        object_id,
        electoral_district_id,
        sequence_order,
        VoteVariationType.n_of_m,
        number_elected,
        votes_allowed,
        draw(txt),
        selection_descriptions,
    )

    placeholder_selections = generate_placeholder_selections_from(
        contest_description, number_elected)

    return (
        object_id,
        contest_description_with_placeholders_from(contest_description,
                                                   placeholder_selections),
    )
Exemple #2
0
    def test_encrypt_contest_manually_formed_contest_description_valid_succeeds(
            self):
        description = ContestDescription(
            object_id="[email protected]",
            electoral_district_id="[email protected]",
            sequence_order=1,
            vote_variation=VoteVariationType.n_of_m,
            number_elected=1,
            votes_allowed=1,
            name="",
            ballot_selections=[
                SelectionDescription(
                    object_id="[email protected]",
                    candidate_id="*****@*****.**",
                    sequence_order=0,
                ),
                SelectionDescription(
                    object_id="[email protected]",
                    candidate_id="*****@*****.**",
                    sequence_order=1,
                ),
            ],
            ballot_title=None,
            ballot_subtitle=None,
        )

        keypair = elgamal_keypair_from_secret(TWO_MOD_Q)
        seed = ONE_MOD_Q

        ####################
        data = ballot_factory.get_random_contest_from(description, Random(0))

        placeholders = generate_placeholder_selections_from(
            description, description.number_elected)
        description_with_placeholders = contest_description_with_placeholders_from(
            description, placeholders)

        # Act
        subject = encrypt_contest(
            data,
            description_with_placeholders,
            keypair.public_key,
            ONE_MOD_Q,
            seed,
            should_verify_proofs=True,
        )
        self.assertIsNotNone(subject)
Exemple #3
0
    def test_encrypt_contest_duplicate_selection_object_ids_fails(self):
        """
        This is an example test of a failing test where the contest description
        is malformed
        """

        description = ContestDescription(
            object_id="[email protected]",
            electoral_district_id="[email protected]",
            sequence_order=1,
            vote_variation=VoteVariationType.n_of_m,
            number_elected=1,
            votes_allowed=1,
            name="",
            ballot_selections=[
                SelectionDescription(
                    object_id="[email protected]",
                    candidate_id="*****@*****.**",
                    sequence_order=0,
                ),
                # Note the selection description is the same as the first sequence element
                SelectionDescription(
                    object_id="[email protected]",
                    candidate_id="*****@*****.**",
                    sequence_order=1,
                ),
            ],
        )

        keypair = elgamal_keypair_from_secret(TWO_MOD_Q)
        seed = ONE_MOD_Q

        # Bypass checking the validity of the description
        data = ballot_factory.get_random_contest_from(
            description, Random(0), suppress_validity_check=True)

        placeholders = generate_placeholder_selections_from(
            description, description.number_elected)
        description_with_placeholders = contest_description_with_placeholders_from(
            description, placeholders)

        # Act
        subject = encrypt_contest(data, description_with_placeholders,
                                  keypair.public_key, ONE_MOD_Q, seed)
        self.assertIsNone(subject)
Exemple #4
0
    def test_decrypt_contest_invalid_input_fails(
        self,
        contest_description: Tuple[str, ContestDescription],
        keypair: ElGamalKeyPair,
        nonce_seed: ElementModQ,
        random_seed: int,
    ):

        # Arrange
        _, description = contest_description
        random = Random(random_seed)
        data = ballot_factory.get_random_contest_from(description, random)

        placeholders = generate_placeholder_selections_from(
            description, description.number_elected
        )
        description_with_placeholders = contest_description_with_placeholders_from(
            description, placeholders
        )

        self.assertTrue(description_with_placeholders.is_valid())

        # Act
        subject = encrypt_contest(
            data,
            description_with_placeholders,
            keypair.public_key,
            ONE_MOD_Q,
            nonce_seed,
        )
        self.assertIsNotNone(subject)

        # tamper with the nonce
        subject.nonce = int_to_q_unchecked(1)

        result_from_nonce = decrypt_contest_with_nonce(
            subject,
            description_with_placeholders,
            keypair.public_key,
            ONE_MOD_Q,
            remove_placeholders=False,
        )
        result_from_nonce_seed = decrypt_contest_with_nonce(
            subject,
            description_with_placeholders,
            keypair.public_key,
            ONE_MOD_Q,
            nonce_seed,
            remove_placeholders=False,
        )

        # Assert
        self.assertIsNone(result_from_nonce)
        self.assertIsNone(result_from_nonce_seed)

        # Tamper with the encryption
        subject.ballot_selections[0].ciphertext = ElGamalCiphertext(
            TWO_MOD_P, TWO_MOD_P
        )

        result_from_key_tampered = decrypt_contest_with_secret(
            subject,
            description_with_placeholders,
            keypair.public_key,
            keypair.secret_key,
            ONE_MOD_Q,
            remove_placeholders=False,
        )
        result_from_nonce_tampered = decrypt_contest_with_nonce(
            subject,
            description_with_placeholders,
            keypair.public_key,
            ONE_MOD_Q,
            remove_placeholders=False,
        )
        result_from_nonce_seed_tampered = decrypt_contest_with_nonce(
            subject,
            description_with_placeholders,
            keypair.public_key,
            ONE_MOD_Q,
            nonce_seed,
            remove_placeholders=False,
        )

        # Assert
        self.assertIsNone(result_from_key_tampered)
        self.assertIsNone(result_from_nonce_tampered)
        self.assertIsNone(result_from_nonce_seed_tampered)
Exemple #5
0
    def test_decrypt_contest_valid_input_succeeds(
        self,
        contest_description: Tuple[str, ContestDescription],
        keypair: ElGamalKeyPair,
        nonce_seed: ElementModQ,
        random_seed: int,
    ):

        # Arrange
        _, description = contest_description
        random = Random(random_seed)
        data = ballot_factory.get_random_contest_from(description, random)

        placeholders = generate_placeholder_selections_from(
            description, description.number_elected
        )
        description_with_placeholders = contest_description_with_placeholders_from(
            description, placeholders
        )

        self.assertTrue(description_with_placeholders.is_valid())

        # Act
        subject = encrypt_contest(
            data,
            description_with_placeholders,
            keypair.public_key,
            ONE_MOD_Q,
            nonce_seed,
        )
        self.assertIsNotNone(subject)

        # Decrypt the contest, but keep the placeholders
        # so we can verify the selection count matches as expected in the test
        result_from_key = decrypt_contest_with_secret(
            subject,
            description_with_placeholders,
            keypair.public_key,
            keypair.secret_key,
            ONE_MOD_Q,
            remove_placeholders=False,
        )
        result_from_nonce = decrypt_contest_with_nonce(
            subject,
            description_with_placeholders,
            keypair.public_key,
            ONE_MOD_Q,
            remove_placeholders=False,
        )
        result_from_nonce_seed = decrypt_contest_with_nonce(
            subject,
            description_with_placeholders,
            keypair.public_key,
            ONE_MOD_Q,
            nonce_seed,
            remove_placeholders=False,
        )

        # Assert
        self.assertIsNotNone(result_from_key)
        self.assertIsNotNone(result_from_nonce)
        self.assertIsNotNone(result_from_nonce_seed)

        # The decrypted contest should include an entry for each possible selection
        # and placeholders for each seat
        expected_entries = (
            len(description.ballot_selections) + description.number_elected
        )
        self.assertTrue(
            result_from_key.is_valid(
                description.object_id,
                expected_entries,
                description.number_elected,
                description.votes_allowed,
            )
        )
        self.assertTrue(
            result_from_nonce.is_valid(
                description.object_id,
                expected_entries,
                description.number_elected,
                description.votes_allowed,
            )
        )
        self.assertTrue(
            result_from_nonce_seed.is_valid(
                description.object_id,
                expected_entries,
                description.number_elected,
                description.votes_allowed,
            )
        )

        # Assert the ballot selections sum to the expected number of selections
        key_selected = sum(
            [selection.vote for selection in result_from_key.ballot_selections]
        )
        nonce_selected = sum(
            [selection.vote for selection in result_from_nonce.ballot_selections]
        )
        seed_selected = sum(
            [selection.vote for selection in result_from_nonce_seed.ballot_selections]
        )

        self.assertEqual(key_selected, nonce_selected)
        self.assertEqual(seed_selected, nonce_selected)
        self.assertEqual(description.number_elected, key_selected)

        # Assert each selection is valid
        for selection_description in description.ballot_selections:

            key_selection = [
                selection
                for selection in result_from_key.ballot_selections
                if selection.object_id == selection_description.object_id
            ][0]
            nonce_selection = [
                selection
                for selection in result_from_nonce.ballot_selections
                if selection.object_id == selection_description.object_id
            ][0]
            seed_selection = [
                selection
                for selection in result_from_nonce_seed.ballot_selections
                if selection.object_id == selection_description.object_id
            ][0]

            data_selections_exist = [
                selection
                for selection in data.ballot_selections
                if selection.object_id == selection_description.object_id
            ]

            # It's possible there are no selections in the original data collection
            # since it is valid to pass in a ballot that is not complete
            if any(data_selections_exist):
                self.assertTrue(data_selections_exist[0].vote == key_selection.vote)
                self.assertTrue(data_selections_exist[0].vote == nonce_selection.vote)
                self.assertTrue(data_selections_exist[0].vote == seed_selection.vote)

            # 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))