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