Skip to content

Commit cb46fb6

Browse files
authored
Merge pull request #172 from microsoft/feature/ballot-decryption-functions
Refactor decryption to support individual ballots
2 parents 46b5f6b + d1d4853 commit cb46fb6

5 files changed

Lines changed: 409 additions & 151 deletions

File tree

src/electionguard/decrypt_with_shares.py

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
from .ballot import CiphertextAcceptedBallot, CiphertextSelection
44
from .decryption_share import (
5-
TallyDecryptionShare,
5+
BallotDecryptionShare,
66
CiphertextDecryptionSelection,
7-
get_spoiled_shares_for_selection,
7+
TallyDecryptionShare,
8+
get_ballot_shares_for_selection,
89
get_tally_shares_for_selection,
910
)
1011
from .dlog import discrete_log
@@ -156,28 +157,52 @@ def decrypt_spoiled_ballots(
156157
] = {}
157158

158159
for spoiled_ballot in spoiled_ballots.values():
159-
contests: Dict[CONTEST_ID, PlaintextTallyContest] = {}
160-
for contest in spoiled_ballot.contests:
161-
selections: Dict[SELECTION_ID, PlaintextTallySelection] = {}
162-
for selection in contest.ballot_selections:
163-
spoiled_shares = get_spoiled_shares_for_selection(
164-
spoiled_ballot.object_id, selection.object_id, shares
165-
)
166-
plaintext_selection = decrypt_selection_with_decryption_shares(
167-
selection, spoiled_shares, extended_base_hash
168-
)
160+
ballot_shares: Dict[AVAILABLE_GUARDIAN_ID, BallotDecryptionShare] = {
161+
guardian_id: share.spoiled_ballots[spoiled_ballot.object_id]
162+
for guardian_id, share in shares.items()
163+
}
164+
165+
decrypted_ballot = decrypt_ballot(
166+
spoiled_ballot, ballot_shares, extended_base_hash
167+
)
168+
if decrypted_ballot:
169+
plaintext_spoiled_ballots[spoiled_ballot.object_id] = decrypted_ballot
170+
else:
171+
return None
172+
173+
return plaintext_spoiled_ballots
169174

170-
# verify the plaintext values are received and add them to the collection
171-
if plaintext_selection is None:
172-
log_warning(
173-
f"could not decrypt spoiled ballot {spoiled_ballot.object_id} for contest {contest.object_id} selection {selection.object_id}"
174-
)
175-
return None
176-
selections[plaintext_selection.object_id] = plaintext_selection
177175

178-
contests[contest.object_id] = PlaintextTallyContest(
179-
contest.object_id, selections
176+
def decrypt_ballot(
177+
ballot: CiphertextAcceptedBallot,
178+
shares: Dict[AVAILABLE_GUARDIAN_ID, BallotDecryptionShare],
179+
extended_base_hash: ElementModQ,
180+
) -> Optional[Dict[CONTEST_ID, PlaintextTallyContest]]:
181+
"""
182+
Try to decrypt a single ballot using the provided decryption shares
183+
"""
184+
185+
contests: Dict[CONTEST_ID, PlaintextTallyContest] = {}
186+
for contest in ballot.contests:
187+
selections: Dict[SELECTION_ID, PlaintextTallySelection] = {}
188+
for selection in contest.ballot_selections:
189+
selection_shares = get_ballot_shares_for_selection(
190+
selection.object_id, shares
191+
)
192+
plaintext_selection = decrypt_selection_with_decryption_shares(
193+
selection, selection_shares, extended_base_hash
180194
)
181-
plaintext_spoiled_ballots[spoiled_ballot.object_id] = contests
182195

183-
return plaintext_spoiled_ballots
196+
# verify the plaintext values are received and add them to the collection
197+
if plaintext_selection is None:
198+
log_warning(
199+
f"could not decrypt ballot {ballot.object_id} for contest {contest.object_id} selection {selection.object_id}"
200+
)
201+
return None
202+
selections[plaintext_selection.object_id] = plaintext_selection
203+
204+
contests[contest.object_id] = PlaintextTallyContest(
205+
contest.object_id, selections
206+
)
207+
208+
return contests

0 commit comments

Comments
 (0)