Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/electionguard/ballot_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def submit_ballot_to_box(
that the ballot has not already been cast or spoiled.
:return: a `SubmittedBallot` or `None` if there was an error
"""
if not ballot_is_valid_for_election(ballot, internal_manifest, context):
if not ballot_is_valid_for_election(ballot, internal_manifest, context, True):
log_warning(f"ballot: {ballot.object_id} failed validity check")
return None

Expand Down
20 changes: 11 additions & 9 deletions src/electionguard/ballot_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ def ballot_is_valid_for_election(
ballot: CiphertextBallot,
internal_manifest: InternalManifest,
context: CiphertextElectionContext,
should_validate: bool,
) -> bool:
"""
Determine if a ballot is valid for a given election
Expand All @@ -20,15 +21,16 @@ def ballot_is_valid_for_election(
if not ballot_is_valid_for_style(ballot, internal_manifest):
return False

if not ballot.is_valid_encryption(
internal_manifest.manifest_hash,
context.elgamal_public_key,
context.crypto_extended_base_hash,
):
log_warning(
f"ballot_is_valid_for_election: mismatching ballot encryption {ballot.object_id}"
)
return False
if should_validate:
if not ballot.is_valid_encryption(
internal_manifest.manifest_hash,
context.elgamal_public_key,
context.crypto_extended_base_hash,
):
log_warning(
f"ballot_is_valid_for_election: mismatching ballot encryption {ballot.object_id}"
)
return False

return True

Expand Down
14 changes: 9 additions & 5 deletions src/electionguard/tally.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,10 @@ def __contains__(self, item: object) -> bool:
return False

def append(
self, ballot: SubmittedBallot, scheduler: Optional[Scheduler] = None
self,
ballot: SubmittedBallot,
should_validate: bool,
scheduler: Optional[Scheduler] = None,
) -> bool:
"""
Append a ballot to the tally and recalculate the tally.
Expand All @@ -240,7 +243,7 @@ def append(
return False

if not ballot_is_valid_for_election(
ballot, self._internal_manifest, self._encryption
ballot, self._internal_manifest, self._encryption, should_validate
):
return False

Expand All @@ -256,6 +259,7 @@ def append(
def batch_append(
self,
ballots: Iterable[Tuple[Any, SubmittedBallot]],
should_validate: bool,
scheduler: Optional[Scheduler] = None,
) -> bool:
"""
Expand All @@ -268,7 +272,7 @@ def batch_append(
# get the value of the dict
ballot_value = ballot[1]
if not self.__contains__(ballot) and ballot_is_valid_for_election(
ballot_value, self._internal_manifest, self._encryption
ballot_value, self._internal_manifest, self._encryption, should_validate
):
if ballot_value.state == BallotBoxState.CAST:

Expand Down Expand Up @@ -431,7 +435,7 @@ def tally_ballot(
)
return None

if tally.append(ballot):
if tally.append(ballot, True):
return tally

return None
Expand All @@ -450,6 +454,6 @@ def tally_ballots(
tally: CiphertextTally = CiphertextTally(
"election-results", internal_manifest, context
)
if tally.batch_append(store):
if tally.batch_append(store, True):
return tally
return None
2 changes: 1 addition & 1 deletion src/electionguard_cli/cli_steps/tally_step.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def _get_tally(
build_election_results.context,
)
with Scheduler() as scheduler:
tally.batch_append(ballots, scheduler)
tally.batch_append(ballots, True, scheduler)
self.print_value("Ballots in tally", tally.__len__())
return tally

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def run(self, db: Database, decryption: DecryptionDto) -> None:
spoiled_ballots = [
ballot for ballot in ballots if ballot.state == BallotBoxState.SPOILED
]
ciphertext_tally = get_tally(manifest, context, ballots)
ciphertext_tally = get_tally(manifest, context, ballots, False)
decryption_share = guardian.compute_tally_share(ciphertext_tally, context)
if decryption_share is None:
raise Exception("No decryption_shares found")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ class DecryptionS2AnnounceService(DecryptionStageBase):
"""Responsible for the 2nd stage in decryptions where the admin announces guardian decryptions"""

def should_run(self, db: Database, decryption: DecryptionDto) -> bool:
isAdmin = self._auth_service.is_admin()
allGuardiansJoined = len(decryption.guardians_joined) >= decryption.guardians
isCompleted = decryption.completed_at_utc is not None
return isAdmin and allGuardiansJoined and not isCompleted
is_admin = self._auth_service.is_admin()
all_guardians_joined = len(decryption.guardians_joined) >= decryption.guardians
is_completed = decryption.completed_at_utc is not None
return is_admin and all_guardians_joined and not is_completed

def run(self, db: Database, decryption: DecryptionDto) -> None:
self._log.info(f"S2: Announcing decryption {decryption.decryption_id}")
Expand Down Expand Up @@ -47,7 +47,7 @@ def run(self, db: Database, decryption: DecryptionDto) -> None:
spoiled_ballots = [
ballot for ballot in ballots if ballot.state == BallotBoxState.SPOILED
]
ciphertext_tally = get_tally(manifest, context, ballots)
ciphertext_tally = get_tally(manifest, context, ballots, True)
self._log.debug("getting plaintext tally")
plaintext_tally = decryption_mediator.get_plaintext_tally(
ciphertext_tally, manifest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ def get_tally(
manifest: Manifest,
context: CiphertextElectionContext,
ballots: List[SubmittedBallot],
should_validate: bool,
) -> CiphertextTally:
internal_manifest = InternalManifest(manifest)

Expand All @@ -69,5 +70,5 @@ def get_tally(
)
ballot_tuples = [(None, ballot) for ballot in ballots]
with Scheduler() as scheduler:
tally.batch_append(ballot_tuples, scheduler)
tally.batch_append(ballot_tuples, should_validate, scheduler)
return tally
2 changes: 1 addition & 1 deletion src/electionguard_verify/verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def verify_aggregation(
new_tally = CiphertextTally("verify", InternalManifest(manifest), context)

for ballot in submitted_ballots:
new_tally.append(ballot)
new_tally.append(ballot, True)

if (
isinstance(tally, CiphertextTally)
Expand Down
22 changes: 11 additions & 11 deletions tests/property/test_tally.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,15 +175,15 @@ def test_tally_ballot_invalid_input_fails(

# verify an UNKNOWN state ballot fails
self.assertIsNone(tally_ballot(first_ballot, tally))
self.assertFalse(tally.append(first_ballot))
self.assertFalse(tally.append(first_ballot, True))

# cast a ballot
first_ballot.state = BallotBoxState.CAST
self.assertTrue(tally.append(first_ballot))
self.assertTrue(tally.append(first_ballot, False))

# try to append a spoiled ballot
first_ballot.state = BallotBoxState.SPOILED
self.assertFalse(tally.append(first_ballot))
self.assertFalse(tally.append(first_ballot, True))

# Verify accumulation fails if the selection collection is empty
if first_ballot.state == BallotBoxState.CAST:
Expand Down Expand Up @@ -217,12 +217,12 @@ def test_tally_ballot_invalid_input_fails(

# verify a cast ballot cannot be added twice
first_ballot.state = BallotBoxState.CAST
self.assertTrue(tally.append(first_ballot))
self.assertFalse(tally.append(first_ballot))
self.assertTrue(tally.append(first_ballot, True))
self.assertFalse(tally.append(first_ballot, False))

# verify an already submitted ballot cannot be changed or readded
first_ballot.state = BallotBoxState.SPOILED
self.assertFalse(tally.append(first_ballot))
self.assertFalse(tally.append(first_ballot, True))

@staticmethod
def _decrypt_with_secret(
Expand Down Expand Up @@ -255,7 +255,7 @@ def _cannot_erroneously_mutate_state(
ballot.contests[0].ballot_selections.remove(first_selection)

self.assertIsNone(tally_ballot(ballot, tally))
self.assertFalse(tally.append(ballot))
self.assertFalse(tally.append(ballot, True))

# Verify accumulation fails if the selection count does not match
if ballot.state == BallotBoxState.CAST:
Expand All @@ -278,15 +278,15 @@ def _cannot_erroneously_mutate_state(
first_contest_hash = ballot.contests[0].description_hash
ballot.contests[0].description_hash = ONE_MOD_Q
self.assertIsNone(tally_ballot(ballot, tally))
self.assertFalse(tally.append(ballot))
self.assertFalse(tally.append(ballot, True))

ballot.contests[0].description_hash = first_contest_hash

# modify a contest object id
first_contest_object_id = ballot.contests[0].object_id
ballot.contests[0].object_id = "a-bad-object-id"
self.assertIsNone(tally_ballot(ballot, tally))
self.assertFalse(tally.append(ballot))
self.assertFalse(tally.append(ballot, True))

ballot.contests[0].object_id = first_contest_object_id

Expand All @@ -297,7 +297,7 @@ def _cannot_erroneously_mutate_state(
ballot.contests[0].ballot_selections[0].object_id = "another-bad-object-id"

self.assertIsNone(tally_ballot(ballot, tally))
self.assertFalse(tally.append(ballot))
self.assertFalse(tally.append(ballot, True))

# Verify accumulation fails if the selection object id does not match
if ballot.state == BallotBoxState.CAST:
Expand All @@ -315,7 +315,7 @@ def _cannot_erroneously_mutate_state(
first_ballot_hash = ballot.manifest_hash
ballot.manifest_hash = ONE_MOD_Q
self.assertIsNone(tally_ballot(ballot, tally))
self.assertFalse(tally.append(ballot))
self.assertFalse(tally.append(ballot, True))
Comment thread
SteveMaier-IRT marked this conversation as resolved.

ballot.manifest_hash = first_ballot_hash
ballot.state = input_state
Expand Down