コード例 #1
0
    def test_encrypt_simple_selection_malformed_data_fails(self):

        # Arrange
        keypair = elgamal_keypair_from_secret(int_to_q(2))
        nonce = randbelow(Q)
        metadata = SelectionDescription("some-selection-object-id",
                                        "some-candidate-id", 1)
        hash_context = metadata.crypto_hash()

        subject = selection_from(metadata)
        self.assertTrue(subject.is_valid(metadata.object_id))

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

        # tamper with the description_hash
        malformed_description_hash = deepcopy(result)
        malformed_description_hash.description_hash = TWO_MOD_Q

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

        # Assert
        self.assertFalse(
            malformed_description_hash.is_valid_encryption(
                hash_context, keypair.public_key))
        self.assertFalse(
            missing_proof.is_valid_encryption(hash_context,
                                              keypair.public_key))
コード例 #2
0
    def encrypt(self, ballot: dict, deterministic: bool = False) -> dict:
        if not self.context.joint_key:
            raise MissingJointKey()

        ballot_style: str = self.context.election.ballot_styles[0].object_id
        contests: List[PlaintextBallotContest] = []

        for contest in self.context.election_metadata.get_contests_for(
                ballot_style):
            selections: List[PlaintextBallotSelection] = [
                selection_from(
                    selection, False, selection.object_id
                    in ballot[contest.object_id])
                for selection in contest.ballot_selections
            ]

            contests.append(
                PlaintextBallotContest(contest.object_id, selections))

        plaintext_ballot = PlaintextBallot(self.ballot_id, ballot_style,
                                           contests)

        # TODO: store the audit information somewhere

        encrypted_ballot = serialize(
            encrypt_ballot(plaintext_ballot, self.context.election_metadata,
                           self.context.election_context, ElementModQ(0),
                           self.context.joint_key if deterministic else None,
                           True))

        return encrypted_ballot
コード例 #3
0
def plaintext_voted_ballot(draw: _DrawType,
                           metadata: InternalElectionDescription):
    """
    Given an `InternalElectionDescription` object, generates an arbitrary `PlaintextBallot` with the
    choices made randomly.
    :param draw: Hidden argument, used by Hypothesis.
    :param metadata: Any `InternalElectionDescription`
    """

    num_ballot_styles = len(metadata.ballot_styles)
    assert num_ballot_styles > 0, "invalid election with no ballot styles"

    # pick a ballot style at random
    ballot_style = metadata.ballot_styles[draw(
        integers(0, num_ballot_styles - 1))]

    contests = metadata.get_contests_for(ballot_style.object_id)
    assert len(contests) > 0, "invalid ballot style with no contests in it"

    voted_contests: List[PlaintextBallotContest] = []
    for contest in contests:
        assert contest.is_valid(), "every contest needs to be valid"
        n = contest.number_elected  # we need exactly this many 1's, and the rest 0's
        ballot_selections = contest.ballot_selections
        assert len(ballot_selections) >= n

        random = Random(draw(integers()))
        random.shuffle(ballot_selections)
        cut_point = draw(integers(0, n))
        yes_votes = ballot_selections[:cut_point]
        no_votes = ballot_selections[cut_point:]

        voted_selections = [
            selection_from(
                description, is_placeholder=False, is_affirmative=True)
            for description in yes_votes
        ] + [
            selection_from(
                description, is_placeholder=False, is_affirmative=False)
            for description in no_votes
        ]

        voted_contests.append(
            PlaintextBallotContest(contest.object_id, voted_selections))

    return PlaintextBallot(str(draw(uuids())), ballot_style.object_id,
                           voted_contests)
コード例 #4
0
    def get_random_selection_from(
        description: SelectionDescription,
        random_source: Random,
        is_placeholder=False,
    ) -> PlaintextBallotSelection:

        selected = bool(random_source.randint(0, 1))
        return selection_from(description, is_placeholder, selected)
コード例 #5
0
    def test_encrypt_simple_selection_succeeds(self):

        # Arrange
        keypair = elgamal_keypair_from_secret(int_to_q(2))
        nonce = randbelow(Q)
        metadata = SelectionDescription("some-selection-object-id",
                                        "some-candidate-id", 1)
        hash_context = metadata.crypto_hash()

        subject = selection_from(metadata)
        self.assertTrue(subject.is_valid(metadata.object_id))

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

        # Assert
        self.assertIsNotNone(result)
        self.assertIsNotNone(result.message)
        self.assertTrue(
            result.is_valid_encryption(hash_context, keypair.public_key))
コード例 #6
0
    def get_random_contest_from(
        self,
        description: ContestDescription,
        random: Random,
        suppress_validity_check=False,
        with_trues=False,
    ) -> PlaintextBallotContest:
        """
        Get a randomly filled contest for the given description that 
        may be undervoted and may include explicitly false votes.
        Since this is only used for testing, the random number generator
        (`random`) must be provided to make this function deterministic.
        """
        if not suppress_validity_check:
            assert description.is_valid(
            ), "the contest description must be valid"

        selections: List[PlaintextBallotSelection] = list()

        voted = 0

        for selection_description in description.ballot_selections:
            selection = self.get_random_selection_from(selection_description,
                                                       random)
            # the caller may force a true value
            voted += selection.to_int()
            if voted <= 1 and selection.to_int() and with_trues:
                selections.append(selection)
                continue

            # Possibly append the true selection, indicating an undervote
            if voted <= description.number_elected and bool(
                    random.randint(0, 1)) == 1:
                selections.append(selection)
            # Possibly append the false selections as well, indicating some choices
            # may be explicitly false
            elif bool(random.randint(0, 1)) == 1:
                selections.append(selection_from(selection_description))

        return PlaintextBallotContest(description.object_id, selections)
コード例 #7
0
    def row_to_plaintext_ballot(self, row: Dict[str, Any]) -> PlaintextBallot:
        ballot_type = row["BallotType"]
        ballot_id = row["BallotId"]
        pbcontests: List[PlaintextBallotContest] = []

        contest_titles: Set[str] = self.style_map[ballot_type]
        for title in contest_titles:
            # This is insanely complicated. The challenge is that we have the Dominion data structures,
            # which has its own column names, but we have to connect that with all of the ElectionGuard
            # structures, which don't just let you follow from one to the other. Instead, it's a twisty
            # world of object_ids. Thus, we need mappings to go from one to the next, and have to do all
            # this extra bookkeeping, in Python dictionaries, to make all the connections.

            contest = self.contest_map[title]
            candidate_ids = [s.candidate_id for s in contest.ballot_selections]
            column_names = [
                self.all_candidate_ids_to_columns[c] for c in candidate_ids
            ]
            voter_intents = [row[x] > 0 for x in column_names]
            selections: List[SelectionDescription] = contest.ballot_selections
            plaintexts = [
                selection_from(
                    description=selections[i],
                    is_placeholder=False,
                    is_affirmative=voter_intents[i],
                ) for i in range(0, len(selections))
            ]
            pbcontests.append(
                PlaintextBallotContest(
                    object_id=contest.object_id,
                    ballot_selections=plaintexts,
                ))

        return PlaintextBallot(
            object_id=ballot_id,
            ballot_style=self.ballotstyle_map[ballot_type].object_id,
            contests=pbcontests,
        )