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 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)
class TestElections(unittest.TestCase): @settings( deadline=timedelta(milliseconds=2000), suppress_health_check=[HealthCheck.too_slow], max_examples=10, ) @given(election_descriptions()) def test_generators_yield_valid_output(self, ed: ElectionDescription): """ Tests that our Hypothesis election strategies generate "valid" output, also exercises the full stack of `is_valid` methods. """ self.assertTrue(ed.is_valid()) @settings( deadline=timedelta(milliseconds=10000), suppress_health_check=[HealthCheck.too_slow], max_examples=5, # disabling the "shrink" phase, because it runs very slowly phases=[Phase.explicit, Phase.reuse, Phase.generate, Phase.target], ) @given( integers(1, 3).flatmap(lambda n: elections_and_ballots(n)), elements_mod_q(), ) def test_accumulation_encryption_decryption( self, everything: ELECTIONS_AND_BALLOTS_TUPLE_TYPE, nonce: ElementModQ, ): """ Tests that decryption is the inverse of encryption over arbitrarily generated elections and ballots. This test uses an abitrarily generated dataset with a single public-private keypair for the election encryption context. It also manually verifies that homomorphic accumulation works as expected. """ # Arrange election_description, metadata, ballots, secret_key, context = everything # Tally the plaintext ballots for comparison later plaintext_tallies = accumulate_plaintext_ballots(ballots) num_ballots = len(ballots) num_contests = len(metadata.contests) zero_nonce, *nonces = Nonces(nonce)[:num_ballots + 1] self.assertEqual(len(nonces), num_ballots) self.assertTrue(len(metadata.contests) > 0) # Generatea valid encryption of zero encrypted_zero = elgamal_encrypt(0, zero_nonce, context.elgamal_public_key) # Act encrypted_ballots = [] # encrypt each ballot for i in range(num_ballots): encrypted_ballot = encrypt_ballot(ballots[i], metadata, context, SEED_HASH, nonces[i]) encrypted_ballots.append(encrypted_ballot) # sanity check the encryption self.assertIsNotNone(encrypted_ballot) self.assertEqual(num_contests, len(encrypted_ballot.contests)) # decrypt the ballot with secret and verify it matches the plaintext decrypted_ballot = decrypt_ballot_with_secret( ballot=encrypted_ballot, election_metadata=metadata, crypto_extended_base_hash=context.crypto_extended_base_hash, public_key=context.elgamal_public_key, secret_key=secret_key, remove_placeholders=True, ) self.assertEqual(ballots[i], decrypted_ballot) # homomorphically accumualte the encrypted ballot representations encrypted_tallies = _accumulate_encrypted_ballots( encrypted_zero, encrypted_ballots) decrypted_tallies = {} for object_id in encrypted_tallies.keys(): decrypted_tallies[object_id] = encrypted_tallies[ object_id].decrypt(secret_key) # loop through the contest descriptions and verify # the decrypted tallies match the plaintext tallies for contest in metadata.contests: # Sanity check the generated data self.assertTrue(len(contest.ballot_selections) > 0) self.assertTrue(len(contest.placeholder_selections) > 0) decrypted_selection_tallies = [ decrypted_tallies[selection.object_id] for selection in contest.ballot_selections ] decrypted_placeholder_tallies = [ decrypted_tallies[placeholder.object_id] for placeholder in contest.placeholder_selections ] plaintext_tally_values = [ plaintext_tallies[selection.object_id] for selection in contest.ballot_selections ] # verify the plaintext tallies match the decrypted tallies self.assertEqual(decrypted_selection_tallies, plaintext_tally_values) # validate the right number of selections including placeholders across all ballots self.assertEqual( contest.number_elected * num_ballots, sum(decrypted_selection_tallies) + sum(decrypted_placeholder_tallies), )