def _candidate_to_selection_description( candidate: Candidate, sequence_order: int) -> SelectionDescription: """ Given a `Candidate` and its position in a list of candidates, returns an equivalent `SelectionDescription`. The selection's `object_id` will contain the candidates's `object_id` within, but will have a "c-" prefix attached, so you'll be able to tell that they're related. """ return SelectionDescription(f"c-{candidate.object_id}", candidate.get_candidate_id(), sequence_order)
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))
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, ONE_MOD_Q, 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, ONE_MOD_Q ) ) self.assertFalse( missing_proof.is_valid_encryption( hash_context, keypair.public_key, ONE_MOD_Q ) )
def get_selection_description_well_formed( draw: _DrawType, ints=integers(1, 20), emails=emails(), candidate_id: Optional[str] = None, sequence_order: Optional[int] = None, ) -> Tuple[str, SelectionDescription]: if candidate_id is None: candidate_id = draw(emails) object_id = f"{candidate_id}-selection" if sequence_order is None: sequence_order = draw(ints) return (object_id, SelectionDescription(object_id, candidate_id, sequence_order))
def get_fake_election(self) -> ElectionDescription: """ Get a single Fake Election object that is manually constructed with default values """ fake_ballot_style = BallotStyle("some-ballot-style-id") fake_ballot_style.geopolitical_unit_ids = ["some-geopoltical-unit-id"] fake_referendum_ballot_selections = [ # Referendum selections are simply a special case of `candidate` in the object model SelectionDescription("some-object-id-affirmative", "some-candidate-id-1", 0), SelectionDescription("some-object-id-negative", "some-candidate-id-2", 1), ] sequence_order = 0 number_elected = 1 votes_allowed = 1 fake_referendum_contest = ReferendumContestDescription( "some-referendum-contest-object-id", "some-geopoltical-unit-id", sequence_order, VoteVariationType.one_of_m, number_elected, votes_allowed, "some-referendum-contest-name", fake_referendum_ballot_selections, ) fake_candidate_ballot_selections = [ SelectionDescription("some-object-id-candidate-1", "some-candidate-id-1", 0), SelectionDescription("some-object-id-candidate-2", "some-candidate-id-2", 1), SelectionDescription("some-object-id-candidate-3", "some-candidate-id-3", 2), ] sequence_order_2 = 1 number_elected_2 = 2 votes_allowed_2 = 2 fake_candidate_contest = CandidateContestDescription( "some-candidate-contest-object-id", "some-geopoltical-unit-id", sequence_order_2, VoteVariationType.one_of_m, number_elected_2, votes_allowed_2, "some-candidate-contest-name", fake_candidate_ballot_selections, ) fake_election = ElectionDescription( election_scope_id="some-scope-id", type=ElectionType.unknown, start_date=datetime.now(), end_date=datetime.now(), geopolitical_units=[ GeopoliticalUnit( "some-geopoltical-unit-id", "some-gp-unit-name", ReportingUnitType.unknown, ) ], parties=[Party("some-party-id-1"), Party("some-party-id-2")], candidates=[ Candidate("some-candidate-id-1"), Candidate("some-candidate-id-2"), Candidate("some-candidate-id-3"), ], contests=[fake_referendum_contest, fake_candidate_contest], ballot_styles=[fake_ballot_style], ) return fake_election
def _contest_name_to_description( self, name: str, contest_uid_maker: UidMaker, gp_uid_maker: UidMaker, ) -> Tuple[ContestDescription, List[Candidate], GeopoliticalUnit, Dict[ str, str]]: selections: List[SelectionDescription] = [] candidates: List[Candidate] = [] # We're collecting the candidates at the same time that we're collecting the selections # to make sure that the object_id's are properly synchronized. Consider the case where # we ended up with two candidates with identical names. In the Dominion CSV, we'd have # no way to tell if they were actually the same person running in two separate contests, # or whether they were distinct. This solution ensures that we create a fresh Candidate # one-to-one with every SelectionDescription. # This method will be called separately for every contest name, and the resulting lists # of candidates should be merged to make the complete list that goes into an # ElectionDescription. candidate_to_column: Dict[str, str] = {} for c in self.metadata.contest_map[name]: id_str = c.object_id id_number = int( id_str[1:]) # total hack: stripping off the first char candidate = Candidate( object_id=id_str, ballot_name=_str_to_internationalized_text_en(c.choice_name), party_id=c.party_name if c.party_name != "" else None, image_uri=None, ) # To make our lives easier, we're going to use identical object_ids for selections # and candidates, and hopefully this won't break anything. candidates.append(candidate) selections.append( SelectionDescription( object_id=candidate.object_id, candidate_id=candidate.object_id, sequence_order=id_number, )) candidate_to_column[candidate.object_id] = c.to_string() gp = GeopoliticalUnit( object_id=gp_uid_maker.next(), name=name, type=ReportingUnitType.unknown, ) id_number, id_str = contest_uid_maker.next_int() number_elected = self.metadata.max_votes_for_map[name] vote_variation_type = (VoteVariationType.one_of_m if number_elected == 1 else VoteVariationType.n_of_m) vote_allowed = None if number_elected < 2 else number_elected return ( ContestDescription( object_id=id_str, electoral_district_id=gp.object_id, sequence_order=id_number, vote_variation=vote_variation_type, number_elected=number_elected, votes_allowed=vote_allowed, name=name, ballot_selections=selections, ballot_title=_str_to_internationalized_text_en(name), ), candidates, gp, candidate_to_column, )