def test_get_plaintext_tally_all_guardians_present(self, data,
                                                       parties: int,
                                                       contests: int):
        # Arrange
        description = data.draw(election_descriptions(parties, contests))
        builder = ElectionBuilder(self.NUMBER_OF_GUARDIANS, self.QUORUM,
                                  description)
        metadata, context = builder.set_public_key(
            self.joint_public_key).build()

        plaintext_ballots: List[PlaintextBallot] = data.draw(
            plaintext_voted_ballots(metadata, randrange(3, 6)))
        plaintext_tallies = accumulate_plaintext_ballots(plaintext_ballots)

        encrypted_tally = self._generate_encrypted_tally(
            metadata, context, plaintext_ballots)

        subject = DecryptionMediator(metadata, context, encrypted_tally)

        # act
        for guardian in self.guardians:
            self.assertIsNotNone(subject.announce(guardian))

        decrypted_tallies = subject.get_plaintext_tally()
        result = self._convert_to_selections(decrypted_tallies)

        # assert
        self.assertIsNotNone(result)
        self.assertEqual(plaintext_tallies, result)
    def generate(self, number_of_ballots: int = DEFAULT_NUMBER_OF_BALLOTS):
        """
        Generate the sample data set
        """

        rmtree(RESULTS_DIR, ignore_errors=True)

        (
            public_data,
            private_data,
        ) = self.election_factory.get_hamilton_election_with_encryption_context()
        plaintext_ballots = self.ballot_factory.generate_fake_plaintext_ballots_for_election(
            public_data.metadata, number_of_ballots
        )
        self.encrypter = EncryptionMediator(
            public_data.metadata, public_data.context, self.encryption_device
        )

        ciphertext_ballots: List[CiphertextBallot] = []
        for plaintext_ballot in plaintext_ballots:
            ciphertext_ballots.append(
                get_optional(self.encrypter.encrypt(plaintext_ballot))
            )

        ballot_store = BallotStore()
        ballot_box = BallotBox(public_data.metadata, public_data.context, ballot_store)

        accepted_ballots: List[CiphertextAcceptedBallot] = []
        for ballot in ciphertext_ballots:
            if randint(0, 100) % 10 == 0:
                accepted_ballots.append(ballot_box.spoil(ballot))
            else:
                accepted_ballots.append(ballot_box.cast(ballot))

        ciphertext_tally = get_optional(
            tally_ballots(ballot_store, public_data.metadata, public_data.context)
        )

        decrypter = DecryptionMediator(
            public_data.metadata, public_data.context, ciphertext_tally
        )

        for guardian in private_data.guardians:
            decrypter.announce(guardian)

        plaintext_tally = get_optional(decrypter.get_plaintext_tally())

        publish(
            public_data.description,
            public_data.context,
            public_data.constants,
            accepted_ballots,
            ciphertext_tally,
            plaintext_tally,
            public_data.guardians,
        )

        publish_private_data(
            plaintext_ballots, ciphertext_ballots, private_data.guardians
        )
Ejemplo n.º 3
0
    def test_get_plaintext_tally_with_all_guardians_present(
            self, values, parties: int, contests: int):
        # Arrange
        description = values.draw(election_descriptions(parties, contests))
        builder = ElectionBuilder(self.NUMBER_OF_GUARDIANS, self.QUORUM,
                                  description)
        internal_manifest, context = (builder.set_public_key(
            self.joint_public_key.joint_public_key).set_commitment_hash(
                self.joint_public_key.commitment_hash).build())

        plaintext_ballots: List[PlaintextBallot] = values.draw(
            plaintext_voted_ballots(internal_manifest, randrange(3, 6)))
        expected_plaintext_tally = accumulate_plaintext_ballots(
            plaintext_ballots)

        encrypted_tally = self._generate_encrypted_tally(
            internal_manifest, context, plaintext_ballots)

        mediator = DecryptionMediator(self.decryption_mediator_id, context)
        available_guardians = self.guardians
        DecryptionHelper.perform_decryption_setup(available_guardians,
                                                  mediator, context,
                                                  encrypted_tally, [])

        # Act
        plaintext_tally = mediator.get_plaintext_tally(encrypted_tally)
        selections = _convert_to_selections(plaintext_tally)

        # Assert
        self.assertIsNotNone(plaintext_tally)
        self.assertIsNotNone(selections)
        self.assertEqual(expected_plaintext_tally, selections)
Ejemplo n.º 4
0
    def test_announce(self):
        # Arrange
        mediator = DecryptionMediator(
            self.decryption_mediator_id,
            self.context,
        )
        guardian = self.guardians[0]
        guardian_key = self.guardians[0].share_election_public_key()
        tally_share = guardian.compute_tally_share(self.ciphertext_tally,
                                                   self.context)
        ballot_shares = {}

        # Act
        mediator.announce(guardian_key, tally_share, ballot_shares)

        # Assert
        self.assertEqual(len(mediator.get_available_guardians()), 1)

        # Act
        # Announce again
        mediator.announce(guardian_key, tally_share, ballot_shares)

        # Assert
        # Can only announce once
        self.assertEqual(len(mediator.get_available_guardians()), 1)
        # Cannot get plaintext tally or spoiled ballots without a quorum
        self.assertIsNone(mediator.get_plaintext_tally(self.ciphertext_tally))
        self.assertIsNone(
            mediator.get_plaintext_ballots(self.ciphertext_ballots))
    def test_get_plaintext_tally_all_guardians_present_simple(self):
        # Arrange
        subject = DecryptionMediator(self.metadata, self.context, self.ciphertext_tally)

        # act
        for guardian in self.guardians:
            self.assertIsNotNone(subject.announce(guardian))

        decrypted_tallies = subject.get_plaintext_tally()
        result = self._convert_to_selections(decrypted_tallies)

        # assert
        self.assertIsNotNone(result)
        self.assertEqual(self.expected_plaintext_tally, result)
Ejemplo n.º 6
0
    def step_4_decrypt_tally(self) -> None:
        """
        Homomorphically combine the selections made on all of the cast ballots
        and use the Available Guardians to decrypt the combined tally.
        In this way, no individual voter's cast ballot is ever decrypted drectly.
        """

        # Generate a Homomorphically Accumulated Tally of the ballots
        self.ciphertext_tally = get_optional(
            tally_ballots(self.ballot_store, self.metadata, self.context)
        )
        self._assert_message(
            tally_ballots.__qualname__,
            f"""
            - cast: {self.ciphertext_tally.count()} 
            - spoiled: {len(self.ciphertext_tally.spoiled_ballots)}
            Total: {len(self.ciphertext_tally)}
            """,
            self.ciphertext_tally is not None,
        )

        # Configure the Decryption
        self.decrypter = DecryptionMediator(
            self.metadata, self.context, self.ciphertext_tally
        )

        # Announce each guardian as present
        for guardian in self.guardians:
            decryption_share = self.decrypter.announce(guardian)
            self._assert_message(
                DecryptionMediator.announce.__qualname__,
                f"Guardian Present: {guardian.object_id}",
                decryption_share is not None,
            )

        # Get the Plain Text Tally
        self.plaintext_tally = get_optional(self.decrypter.get_plaintext_tally())
        self._assert_message(
            DecryptionMediator.get_plaintext_tally.__qualname__,
            "Tally Decrypted",
            self.plaintext_tally is not None,
        )

        # Now, compare the results
        self.compare_results()
Ejemplo n.º 7
0
    def test_get_plaintext_with_a_missing_guardian(self):

        # Arrange
        mediator = DecryptionMediator(
            self.decryption_mediator_id,
            self.context,
        )

        available_guardians = self.guardians[0:2]
        all_guardian_keys = [
            guardian.share_election_public_key() for guardian in self.guardians
        ]

        DecryptionHelper.perform_compensated_decryption_setup(
            available_guardians,
            all_guardian_keys,
            mediator,
            self.context,
            self.ciphertext_tally,
            self.ciphertext_ballots,
        )

        # Act
        plaintext_tally = mediator.get_plaintext_tally(self.ciphertext_tally)
        plaintext_ballots = mediator.get_plaintext_ballots(
            self.ciphertext_ballots)

        # Convert to selections to check for the same tally
        selections = _convert_to_selections(plaintext_tally)

        # Verify we get the same tally back if we call again
        another_plaintext_tally = mediator.get_plaintext_tally(
            self.ciphertext_tally)

        # Assert
        self.assertIsNotNone(plaintext_tally)
        self.assertIsNotNone(plaintext_ballots)
        self.assertEqual(len(self.ciphertext_ballots), len(plaintext_ballots))

        self.assertIsNotNone(selections)
        self.assertEqual(self.expected_plaintext_tally, selections)

        self.assertEqual(plaintext_tally, another_plaintext_tally)
    def test_decrypt_spoiled_ballots_all_guardians_present(self):
        # Arrange
        # precompute decryption shares for the guardians
        first_share = compute_decryption_share(self.guardians[0],
                                               self.ciphertext_tally,
                                               self.context)
        second_share = compute_decryption_share(self.guardians[1],
                                                self.ciphertext_tally,
                                                self.context)
        third_share = compute_decryption_share(self.guardians[2],
                                               self.ciphertext_tally,
                                               self.context)
        shares = {
            self.guardians[0].object_id: first_share,
            self.guardians[1].object_id: second_share,
            self.guardians[2].object_id: third_share,
        }

        subject = DecryptionMediator(self.metadata, self.context,
                                     self.ciphertext_tally)

        # act
        result = decrypt_spoiled_ballots(
            self.ciphertext_tally.spoiled_ballots,
            shares,
            self.context.crypto_extended_base_hash,
        )

        # assert
        self.assertIsNotNone(result)
        self.assertTrue(self.fake_spoiled_ballot.object_id in result)

        spoiled_ballot = result[self.fake_spoiled_ballot.object_id]
        for contest in self.fake_spoiled_ballot.contests:
            for selection in contest.ballot_selections:
                self.assertEqual(
                    spoiled_ballot[contest.object_id].selections[
                        selection.object_id].plaintext,
                    result[self.fake_spoiled_ballot.object_id][
                        contest.object_id].selections[
                            selection.object_id].plaintext,
                )
    def test_announce(self):
        # Arrange
        subject = DecryptionMediator(self.metadata, self.context,
                                     self.ciphertext_tally)

        # act
        result = subject.announce(self.guardians[0])

        # assert
        self.assertIsNotNone(result)

        # Can only announce once
        self.assertIsNotNone(subject.announce(self.guardians[0]))

        # Cannot submit another share internally
        self.assertFalse(
            subject._submit_decryption_share(
                TallyDecryptionShare(self.guardians[0].object_id, ZERO_MOD_P,
                                     {}, {})))

        # Cannot get plaintext tally without a quorum
        self.assertIsNone(subject.get_plaintext_tally())
Ejemplo n.º 10
0
    def test_get_plaintext_with_all_guardians_present(self):
        # Arrange
        mediator = DecryptionMediator(
            self.decryption_mediator_id,
            self.context,
        )

        available_guardians = self.guardians

        DecryptionHelper.perform_decryption_setup(
            available_guardians,
            mediator,
            self.context,
            self.ciphertext_tally,
            self.ciphertext_ballots,
        )

        # Act
        plaintext_tally = mediator.get_plaintext_tally(self.ciphertext_tally)
        plaintext_ballots = mediator.get_plaintext_ballots(
            self.ciphertext_ballots)

        # Convert to selections to check for the same tally
        selections = _convert_to_selections(plaintext_tally)

        # Verify we get the same tally back if we call again
        another_plaintext_tally = mediator.get_plaintext_tally(
            self.ciphertext_tally)

        # Assert
        self.assertIsNotNone(plaintext_tally)
        self.assertIsNotNone(plaintext_ballots)
        self.assertEqual(len(self.ciphertext_ballots), len(plaintext_ballots))

        self.assertIsNotNone(selections)
        self.assertEqual(self.expected_plaintext_tally, selections)

        self.assertEqual(plaintext_tally, another_plaintext_tally)
    def test_get_plaintext_tally_compensate_missing_guardian_simple(self):

        # Arrange
        subject = DecryptionMediator(self.metadata, self.context, self.ciphertext_tally)

        # Act

        self.assertIsNotNone(subject.announce(self.guardians[0]))
        self.assertIsNotNone(subject.announce(self.guardians[1]))

        # explicitly compensate to demonstrate that this is possible, but not required
        self.assertIsNotNone(
            subject.compensate(self.guardians[2].object_id, identity_auxiliary_decrypt)
        )

        decrypted_tallies = subject.get_plaintext_tally()
        self.assertIsNotNone(decrypted_tallies)
        result = self._convert_to_selections(decrypted_tallies)

        # assert
        self.assertIsNotNone(result)
        print(result)
        self.assertEqual(self.expected_plaintext_tally, result)
    def step_4_decrypt_tally(self) -> None:
        """
        Homomorphically combine the selections made on all of the cast ballots
        and use the Available Guardians to decrypt the combined tally.
        In this way, no individual voter's cast ballot is ever decrypted drectly.
        """

        # Generate a Homomorphically Accumulated Tally of the ballots
        self.ciphertext_tally = get_optional(
            tally_ballots(self.ballot_store, self.internal_manifest,
                          self.context))
        self.ciphertext_ballots = get_ballots(self.ballot_store,
                                              BallotBoxState.SPOILED)
        self._assert_message(
            tally_ballots.__qualname__,
            f"""
            - cast: {self.ciphertext_tally.cast()}
            - spoiled: {self.ciphertext_tally.spoiled()}
            Total: {len(self.ciphertext_tally)}
            """,
            self.ciphertext_tally is not None,
        )

        # Configure the Decryption
        ciphertext_ballots = list(self.ciphertext_ballots.values())
        self.decryption_mediator = DecryptionMediator(
            "decryption-mediator",
            self.context,
        )

        # Announce each guardian as present
        count = 0
        for guardian in self.guardians:
            guardian_key = guardian.share_election_public_key()
            tally_share = guardian.compute_tally_share(self.ciphertext_tally,
                                                       self.context)
            ballot_shares = guardian.compute_ballot_shares(
                ciphertext_ballots, self.context)
            self.decryption_mediator.announce(guardian_key, tally_share,
                                              ballot_shares)
            count += 1
            self._assert_message(
                DecryptionMediator.announce.__qualname__,
                f"Guardian Present: {guardian.id}",
                len(self.decryption_mediator.get_available_guardians()) ==
                count,
            )

        # Get the plaintext Tally
        self.plaintext_tally = get_optional(
            self.decryption_mediator.get_plaintext_tally(
                self.ciphertext_tally))
        self._assert_message(
            DecryptionMediator.get_plaintext_tally.__qualname__,
            "Tally Decrypted",
            self.plaintext_tally is not None,
        )

        # Get the plaintext Spoiled Ballots
        self.plaintext_spoiled_ballots = get_optional(
            self.decryption_mediator.get_plaintext_ballots(ciphertext_ballots))
        self._assert_message(
            DecryptionMediator.get_plaintext_ballots.__qualname__,
            "Spoiled Ballots Decrypted",
            self.plaintext_tally is not None,
        )

        # Now, compare the results
        self.compare_results()
Ejemplo n.º 13
0
    def generate(
        self,
        number_of_ballots: int = DEFAULT_NUMBER_OF_BALLOTS,
        cast_spoil_ratio: int = CAST_SPOIL_RATIO,
    ):
        """
        Generate the sample data set
        """

        # Clear the results directory
        rmtree(RESULTS_DIR, ignore_errors=True)

        # Configure the election
        (
            public_data,
            private_data,
        ) = self.election_factory.get_hamilton_election_with_encryption_context(
        )
        plaintext_ballots = self.ballot_factory.generate_fake_plaintext_ballots_for_election(
            public_data.metadata, number_of_ballots)
        self.encrypter = EncryptionMediator(public_data.metadata,
                                            public_data.context,
                                            self.encryption_device)

        # Encrypt some ballots
        ciphertext_ballots: List[CiphertextBallot] = []
        for plaintext_ballot in plaintext_ballots:
            ciphertext_ballots.append(
                get_optional(self.encrypter.encrypt(plaintext_ballot)))

        ballot_store = BallotStore()
        ballot_box = BallotBox(public_data.metadata, public_data.context,
                               ballot_store)

        # Randomly cast/spoil the ballots
        accepted_ballots: List[CiphertextAcceptedBallot] = []
        for ballot in ciphertext_ballots:
            if randint(0, 100) % cast_spoil_ratio == 0:
                accepted_ballots.append(ballot_box.spoil(ballot))
            else:
                accepted_ballots.append(ballot_box.cast(ballot))

        # Tally
        ciphertext_tally = get_optional(
            tally_ballots(ballot_store, public_data.metadata,
                          public_data.context))

        # Decrypt
        decrypter = DecryptionMediator(public_data.metadata,
                                       public_data.context, ciphertext_tally)

        for i, guardian in enumerate(private_data.guardians):
            if i < QUORUM or not THRESHOLD_ONLY:
                decrypter.announce(guardian)

        plaintext_tally = get_optional(decrypter.get_plaintext_tally())

        # Publish
        publish(
            public_data.description,
            public_data.context,
            public_data.constants,
            [self.encryption_device],
            accepted_ballots,
            ciphertext_tally.spoiled_ballots.values(),
            publish_ciphertext_tally(ciphertext_tally),
            plaintext_tally,
            public_data.guardians,
        )

        publish_private_data(plaintext_ballots, ciphertext_ballots,
                             private_data.guardians)
Ejemplo n.º 14
0
    def generate(
        self,
        number_of_ballots: int = DEFAULT_NUMBER_OF_BALLOTS,
        spoil_rate: int = DEFAULT_SPOIL_RATE,
        use_all_guardians: bool = DEFAULT_USE_ALL_GUARDIANS,
    ):
        """
        Generate the sample data set
        """

        # Clear the results directory
        rmtree(RESULTS_DIR, ignore_errors=True)

        # Configure the election
        (
            manifest,
            private_data,
        ) = self.election_factory.get_hamilton_manifest_with_encryption_context()
        plaintext_ballots = (
            self.ballot_factory.generate_fake_plaintext_ballots_for_election(
                manifest.internal_manifest, number_of_ballots
            )
        )
        self.encrypter = EncryptionMediator(
            manifest.internal_manifest, manifest.context, self.encryption_device
        )

        # Encrypt some ballots
        ciphertext_ballots: List[CiphertextBallot] = []
        for plaintext_ballot in plaintext_ballots:
            ciphertext_ballots.append(
                get_optional(self.encrypter.encrypt(plaintext_ballot))
            )

        ballot_store = DataStore()
        ballot_box = BallotBox(
            manifest.internal_manifest, manifest.context, ballot_store
        )

        # Randomly cast/spoil the ballots
        submitted_ballots: List[SubmittedBallot] = []
        for ballot in ciphertext_ballots:
            if randint(0, 100) < spoil_rate:
                submitted_ballots.append(ballot_box.spoil(ballot))
            else:
                submitted_ballots.append(ballot_box.cast(ballot))

        # Tally
        spoiled_ciphertext_ballots = get_ballots(ballot_store, BallotBoxState.SPOILED)
        ciphertext_tally = get_optional(
            tally_ballots(ballot_store, manifest.internal_manifest, manifest.context)
        )

        # Decrypt
        mediator = DecryptionMediator("sample-manifest-decrypter", manifest.context)
        available_guardians = (
            private_data.guardians
            if use_all_guardians
            else private_data.guardians[0:QUORUM]
        )
        DecryptionHelper.perform_decryption_setup(
            available_guardians,
            mediator,
            manifest.context,
            ciphertext_tally,
            spoiled_ciphertext_ballots,
        )

        plaintext_tally = get_optional(mediator.get_plaintext_tally(ciphertext_tally))
        plaintext_spoiled_ballots = get_optional(
            mediator.get_plaintext_ballots(spoiled_ciphertext_ballots)
        )

        # Publish
        publish(
            manifest.manifest,
            manifest.context,
            manifest.constants,
            [self.encryption_device],
            submitted_ballots,
            plaintext_spoiled_ballots.values(),
            ciphertext_tally.publish(),
            plaintext_tally,
            manifest.guardians,
        )

        publish_private_data(
            plaintext_ballots, ciphertext_ballots, private_data.guardians
        )