コード例 #1
0
    def test_encrypt_contest_valid_input_succeeds(
        self,
        contest_description: ContestDescription,
        keypair: ElGamalKeyPair,
        nonce_seed: ElementModQ,
        random_seed: int,
    ):

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

        # Act
        result = encrypt_contest(subject, description, keypair.public_key,
                                 nonce_seed)

        # Assert
        self.assertIsNotNone(result)
        self.assertTrue(
            result.is_valid_encryption(description.crypto_hash(),
                                       keypair.public_key))

        # The encrypted contest should include an entry for each possible selection
        # and placeholders for each seat
        expected_entries = (len(description.ballot_selections) +
                            description.number_elected)
        self.assertEqual(len(result.ballot_selections), expected_entries)
コード例 #2
0
    def test_encrypt_contest_overvote_fails(
        self,
        contest_description: ContestDescription,
        keypair: ElGamalKeyPair,
        seed: ElementModQ,
        overvotes: int,
        random_seed: int,
    ):
        # Arrange
        _, description = contest_description
        random = Random(random_seed)
        subject = ballot_factory.get_random_contest_from(description, random)

        highest_sequence = max(
            *[
                selection.sequence_order
                for selection in description.ballot_selections
            ],
            1,
        )

        for i in range(overvotes):
            extra = ballot_factory.get_random_selection_from(
                description.ballot_selections[0], random)
            extra.sequence_order = highest_sequence + i + 1
            subject.ballot_selections.append(extra)

        # Act
        result = encrypt_contest(subject, description, keypair.public_key,
                                 seed)

        # Assert
        self.assertIsNone(result)
コード例 #3
0
    def test_encrypt_simple_contest_referendum_succeeds(self):
        # Arrange
        keypair = elgamal_keypair_from_secret(int_to_q(2))
        nonce = randbelow(Q)
        ballot_selections = [
            SelectionDescription(
                "some-object-id-affirmative", "some-candidate-id-affirmative", 0
            ),
            SelectionDescription(
                "some-object-id-negative", "some-candidate-id-negative", 1
            ),
        ]
        placeholder_selections = [
            SelectionDescription(
                "some-object-id-placeholder", "some-candidate-id-placeholder", 2
            )
        ]
        metadata = ContestDescriptionWithPlaceholders(
            "some-contest-object-id",
            "some-electoral-district-id",
            0,
            VoteVariationType.one_of_m,
            1,
            1,
            "some-referendum-contest-name",
            ballot_selections,
            None,
            None,
            placeholder_selections,
        )
        hash_context = metadata.crypto_hash()

        subject = contest_from(metadata)
        self.assertTrue(
            subject.is_valid(
                metadata.object_id,
                len(metadata.ballot_selections),
                metadata.number_elected,
                metadata.votes_allowed,
            )
        )

        # Act
        result = encrypt_contest(
            subject, metadata, keypair.public_key, ONE_MOD_Q, nonce
        )

        # Assert
        self.assertIsNotNone(result)
        self.assertTrue(
            result.is_valid_encryption(hash_context, keypair.public_key, ONE_MOD_Q)
        )
コード例 #4
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
        """
        random_seed = 0

        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)
コード例 #5
0
    def test_encrypt_contest_valid_input_tampered_proof_fails(
        self,
        contest_description: ContestDescription,
        keypair: ElGamalKeyPair,
        nonce_seed: ElementModQ,
        random_seed: int,
    ):

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

        # Act
        result = encrypt_contest(
            subject, description, keypair.public_key, ONE_MOD_Q, nonce_seed
        )
        self.assertTrue(
            result.is_valid_encryption(
                description.crypto_hash(), keypair.public_key, ONE_MOD_Q
            )
        )

        # tamper with the proof
        malformed_proof = deepcopy(result)
        altered_a = mult_p(result.proof.pad, TWO_MOD_P)
        malformed_disjunctive = ConstantChaumPedersenProof(
            altered_a,
            malformed_proof.proof.data,
            malformed_proof.proof.challenge,
            malformed_proof.proof.response,
            malformed_proof.proof.constant,
        )
        malformed_proof.proof = malformed_disjunctive

        # remove the proof
        missing_proof = deepcopy(result)
        missing_proof.proof = None

        # Assert
        self.assertFalse(
            malformed_proof.is_valid_encryption(
                description.crypto_hash(), keypair.public_key, ONE_MOD_Q
            )
        )
        self.assertFalse(
            missing_proof.is_valid_encryption(
                description.crypto_hash(), keypair.public_key, ONE_MOD_Q
            )
        )
コード例 #6
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)
コード例 #7
0
    def test_encrypt_contest_valid_input_tampered_proof_fails(
        self,
        contest_description: ContestDescription,
        keypair: ElGamalKeyPair,
        nonce_seed: ElementModQ,
        random_seed: int,
    ):

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

        # Act
        result = encrypt_contest(subject, description, keypair.public_key,
                                 nonce_seed)
        self.assertTrue(
            result.is_valid_encryption(description.crypto_hash(),
                                       keypair.public_key))

        # tamper with the proof
        malformed_proof = deepcopy(result)
        malformed_disjunctive = malformed_proof.proof._replace(
            a=mult_p(result.proof.a, TWO_MOD_P))
        malformed_proof.proof = malformed_disjunctive

        # remove the proof
        missing_proof = deepcopy(result)
        missing_proof.proof = None

        # Assert
        self.assertFalse(
            malformed_proof.is_valid_encryption(description.crypto_hash(),
                                                keypair.public_key))
        self.assertFalse(
            missing_proof.is_valid_encryption(description.crypto_hash(),
                                              keypair.public_key))
コード例 #8
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, 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,
            remove_placeholders=False,
        )
        result_from_nonce_seed = decrypt_contest_with_nonce(
            subject,
            description_with_placeholders,
            keypair.public_key,
            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].message = 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,
            remove_placeholders=False,
        )
        result_from_nonce_tampered = decrypt_contest_with_nonce(
            subject,
            description_with_placeholders,
            keypair.public_key,
            remove_placeholders=False,
        )
        result_from_nonce_seed_tampered = decrypt_contest_with_nonce(
            subject,
            description_with_placeholders,
            keypair.public_key,
            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)
コード例 #9
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, 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,
            remove_placeholders=False,
        )
        result_from_nonce = decrypt_contest_with_nonce(
            subject,
            description_with_placeholders,
            keypair.public_key,
            remove_placeholders=False,
        )
        result_from_nonce_seed = decrypt_contest_with_nonce(
            subject,
            description_with_placeholders,
            keypair.public_key,
            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.to_int() for selection in result_from_key.ballot_selections]
        )
        nonce_selected = sum(
            [selection.to_int() for selection in result_from_nonce.ballot_selections]
        )
        seed_selected = sum(
            [
                selection.to_int()
                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].to_int() == key_selection.to_int()
                )
                self.assertTrue(
                    data_selections_exist[0].to_int() == nonce_selection.to_int()
                )
                self.assertTrue(
                    data_selections_exist[0].to_int() == seed_selection.to_int()
                )

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