Skip to content

Commit 9e48f5a

Browse files
committed
Decryption Mediator Rework (#353)
* ♻️ Add MEDIATOR_ID type * 🔥 Remove methods forcing multiplicity on ballots Replace methods for simpler single ballot methods that can then be iterated through. This reduces down the scope and gives individuals more flexibility on collections without the use of DataStore * ⚠️ Create a Decryption Helper Create a decryption helper to assist in explaining how to perform the decryption ceremony without the repeated code in various sections * ♻️ Remove available / missing name To reduce the amount of typing here and replace with a singular name instead of adding extra types to exports * ✅ Update decryption test for a single ballot * ✨ Update Guardian to perform own shares Guardian is now in charge of its own shares and uses the functional methods instead of referencing some internal methods. A guardian must perform their own shares and send to mediator * ✨ Update Decryption Mediator Major change to decryption mediator. The mediator now only handles the brief exchanges of shares and reconstructs the missing guardian shares. Missing guardians now also have to be announced separately. Other tests updated to match. Remove commented lines
1 parent d9d4b52 commit 9e48f5a

12 files changed

Lines changed: 756 additions & 813 deletions

src/electionguard/decrypt_with_shares.py

Lines changed: 1 addition & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
PlaintextTallySelection,
1616
)
1717
from .logs import log_warning
18-
from .types import BALLOT_ID, CONTEST_ID, GUARDIAN_ID, SELECTION_ID
18+
from .types import CONTEST_ID, GUARDIAN_ID, SELECTION_ID
1919

2020
AVAILABLE_GUARDIAN_ID = GUARDIAN_ID
2121
MISSING_GUARDIAN_ID = GUARDIAN_ID
@@ -60,41 +60,6 @@ def decrypt_tally(
6060
return PlaintextTally(tally.object_id, contests)
6161

6262

63-
def decrypt_ballots(
64-
ballots: Dict[BALLOT_ID, SubmittedBallot],
65-
guardian_ballot_shares: Dict[
66-
AVAILABLE_GUARDIAN_ID, Dict[BALLOT_ID, DecryptionShare]
67-
],
68-
crypto_extended_base_hash: ElementModQ,
69-
) -> Optional[Dict[BALLOT_ID, PlaintextTally]]:
70-
"""
71-
Try to decrypt each of the spoiled ballots using the provided decryption shares
72-
73-
:param ballots: The SubmittedBallots to decrypt
74-
:param guardian_ballot_shares: The guardian Decryption Shares for all guardians for each ballot
75-
:param crypto_extended_base_hash: The extended base hash
76-
:return: A PlaintextTally or None if there is an error
77-
"""
78-
79-
plaintext_ballots: Dict[BALLOT_ID, PlaintextTally] = {}
80-
81-
for ballot in ballots.values():
82-
# For each guardian, get the specific ballot share
83-
ballot_shares = {
84-
guardian_id: shares[ballot.object_id]
85-
for guardian_id, shares in guardian_ballot_shares.items()
86-
}
87-
88-
decrypted_ballot = decrypt_ballot(
89-
ballot, ballot_shares, crypto_extended_base_hash
90-
)
91-
if not decrypted_ballot:
92-
return None
93-
plaintext_ballots[ballot.object_id] = decrypted_ballot
94-
95-
return plaintext_ballots
96-
97-
9863
def decrypt_ballot(
9964
ballot: SubmittedBallot,
10065
shares: Dict[AVAILABLE_GUARDIAN_ID, DecryptionShare],

src/electionguard/decryption.py

Lines changed: 14 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,7 @@
3737
from .scheduler import Scheduler
3838
from .tally import CiphertextTally
3939

40-
from .types import BALLOT_ID, CONTEST_ID, GUARDIAN_ID, SELECTION_ID
41-
42-
AVAILABLE_GUARDIAN_ID = GUARDIAN_ID
43-
MISSING_GUARDIAN_ID = GUARDIAN_ID
40+
from .types import CONTEST_ID, GUARDIAN_ID, SELECTION_ID
4441

4542
GUARDIAN_PUBLIC_KEY = ElementModP
4643
ELECTION_PUBLIC_KEY = ElementModP
@@ -143,74 +140,6 @@ def compute_compensated_decryption_share(
143140
)
144141

145142

146-
def compute_decryption_share_for_ballots(
147-
guardian_keys: ElectionKeyPair,
148-
ballots: List[SubmittedBallot],
149-
context: CiphertextElectionContext,
150-
scheduler: Optional[Scheduler] = None,
151-
) -> Optional[Dict[BALLOT_ID, DecryptionShare]]:
152-
"""
153-
Compute the decryption for a list of ballots for a guardian
154-
155-
:param guardian_keys: Guardian's election key pair
156-
:param ballots: Ballots to be decrypted
157-
:param context: The public election encryption context
158-
:return: Dictionary of decrypted ballots or `None` if there is an error
159-
"""
160-
shares: Dict[BALLOT_ID, DecryptionShare] = {}
161-
162-
for ballot in ballots:
163-
ballot_share = compute_decryption_share_for_ballot(
164-
guardian_keys, ballot, context, scheduler
165-
)
166-
if ballot_share is None:
167-
return None
168-
shares[ballot.object_id] = ballot_share
169-
170-
return shares
171-
172-
173-
def compute_compensated_decryption_share_for_ballots(
174-
guardian_key: ElectionPublicKey,
175-
guardian_auxiliary_keys: AuxiliaryKeyPair,
176-
missing_guardian_key: ElectionPublicKey,
177-
missing_guardian_backup: ElectionPartialKeyBackup,
178-
ballots: List[SubmittedBallot],
179-
context: CiphertextElectionContext,
180-
decrypt: AuxiliaryDecrypt = rsa_decrypt,
181-
scheduler: Optional[Scheduler] = None,
182-
) -> Optional[Dict[BALLOT_ID, CompensatedDecryptionShare]]:
183-
"""
184-
Compute the compensated decryption for ballots for a guardian
185-
186-
:param guardian_key: The election public key of the available guardian that will partially decrypt the selection
187-
:param guardian_auxiliary_keys: Auxiliary keys for the available guardian
188-
:param missing_guardian_key: Election public key of the guardian that is missing
189-
:param missing_guardian_backup: Election partial key backup of the missing guardian
190-
:param ballots: Ballots to be decrypted
191-
:param context: The public election encryption context
192-
:return: Dictionary of decrypted ballots or `None` if there is an error
193-
"""
194-
shares: Dict[BALLOT_ID, CompensatedDecryptionShare] = {}
195-
196-
for ballot in ballots:
197-
ballot_share = compute_compensated_decryption_share_for_ballot(
198-
guardian_key,
199-
guardian_auxiliary_keys,
200-
missing_guardian_key,
201-
missing_guardian_backup,
202-
ballot,
203-
context,
204-
decrypt,
205-
scheduler,
206-
)
207-
if ballot_share is None:
208-
return None
209-
shares[ballot.object_id] = ballot_share
210-
211-
return shares
212-
213-
214143
def compute_decryption_share_for_ballot(
215144
guardian_keys: ElectionKeyPair,
216145
ballot: SubmittedBallot,
@@ -623,8 +552,8 @@ def compute_recovery_public_key(
623552
def reconstruct_decryption_share(
624553
missing_guardian_key: ElectionPublicKey,
625554
tally: CiphertextTally,
626-
shares: Dict[AVAILABLE_GUARDIAN_ID, CompensatedDecryptionShare],
627-
lagrange_coefficients: Dict[AVAILABLE_GUARDIAN_ID, ElementModQ],
555+
shares: Dict[GUARDIAN_ID, CompensatedDecryptionShare],
556+
lagrange_coefficients: Dict[GUARDIAN_ID, ElementModQ],
628557
) -> DecryptionShare:
629558
"""
630559
Reconstruct the missing Decryption Share for a missing guardian
@@ -633,7 +562,7 @@ def reconstruct_decryption_share(
633562
:param missing_guardian_id: The guardian id for the missing guardian
634563
:param public_key: The public key of the guardian creating share
635564
:param tally: The collection of `CiphertextTallyContest` that is cast
636-
:shares: the collection of `CompensatedTallyDecryptionShare` for the missing guardian
565+
:shares: the collection of `CompensatedTallyDecryptionShare` for the missing guardian from available guardians
637566
:lagrange_coefficients: the lagrange coefficients corresponding to the available guardians that provided shares
638567
"""
639568
contests: Dict[CONTEST_ID, CiphertextDecryptionContest] = {}
@@ -658,40 +587,11 @@ def reconstruct_decryption_share(
658587
)
659588

660589

661-
def reconstruct_decryption_shares_for_ballots(
662-
missing_guardian_key: ElectionPublicKey,
663-
ballots: Dict[BALLOT_ID, SubmittedBallot],
664-
shares: Dict[BALLOT_ID, Dict[AVAILABLE_GUARDIAN_ID, CompensatedDecryptionShare]],
665-
lagrange_coefficients: Dict[AVAILABLE_GUARDIAN_ID, ElementModQ],
666-
) -> Dict[BALLOT_ID, DecryptionShare]:
667-
"""
668-
Reconstruct the missing Decryption shares for a missing guardian from the collection of compensated decryption
669-
shares
670-
671-
:param missing_guardian_key: the public key for the missing guardian
672-
:param ballots: The collection of `SubmittedBallot` that is spoiled
673-
:shares: the collection of `CompensatedDecryptionShare` for the missing guardian
674-
:lagrange_coefficients: the lagrange coefficients corresponding to the available guardians that provided shares
675-
"""
676-
ballot_shares: Dict[BALLOT_ID, DecryptionShare] = {}
677-
678-
for ballot_id, ballot in ballots.items():
679-
ballot_share = reconstruct_decryption_share_for_ballot(
680-
missing_guardian_key,
681-
ballot,
682-
shares[ballot_id],
683-
lagrange_coefficients,
684-
)
685-
ballot_shares[ballot.object_id] = ballot_share
686-
687-
return ballot_shares
688-
689-
690590
def reconstruct_decryption_share_for_ballot(
691591
missing_guardian_key: ElectionPublicKey,
692592
ballot: SubmittedBallot,
693-
shares: Dict[AVAILABLE_GUARDIAN_ID, CompensatedDecryptionShare],
694-
lagrange_coefficients: Dict[AVAILABLE_GUARDIAN_ID, ElementModQ],
593+
shares: Dict[GUARDIAN_ID, CompensatedDecryptionShare],
594+
lagrange_coefficients: Dict[GUARDIAN_ID, ElementModQ],
695595
) -> DecryptionShare:
696596
"""
697597
Reconstruct a missing ballot Decryption share for a missing guardian
@@ -701,7 +601,7 @@ def reconstruct_decryption_share_for_ballot(
701601
:param public_key: the public key for the missing guardian
702602
:param ballot: The `SubmittedBallot` to reconstruct
703603
:shares: the collection of `CompensatedBallotDecryptionShare` for
704-
the missing guardian, each keyed by the ID of the guardian that produced it
604+
the missing guardian, each keyed by the ID of the guardian that produced it from available guardians
705605
:lagrange_coefficients: the lagrange coefficients corresponding to the available guardians that provided shares
706606
"""
707607
contests: Dict[CONTEST_ID, CiphertextDecryptionContest] = {}
@@ -725,24 +625,22 @@ def reconstruct_decryption_share_for_ballot(
725625

726626

727627
def reconstruct_decryption_contest(
728-
missing_guardian_id: MISSING_GUARDIAN_ID,
628+
missing_guardian_id: GUARDIAN_ID,
729629
contest: CiphertextContest,
730-
shares: Dict[AVAILABLE_GUARDIAN_ID, CompensatedDecryptionShare],
731-
lagrange_coefficients: Dict[AVAILABLE_GUARDIAN_ID, ElementModQ],
630+
shares: Dict[GUARDIAN_ID, CompensatedDecryptionShare],
631+
lagrange_coefficients: Dict[GUARDIAN_ID, ElementModQ],
732632
) -> CiphertextDecryptionContest:
733633
"""
734634
Recontruct the missing Decryption Share for a missing guardian
735635
from the collection of compensated decryption shares
736636
737637
:param missing_guardian_id: The guardian id for the missing guardian
738638
:param contest: The CiphertextContest to decrypt
739-
:shares: the collection of `CompensatedDecryptionShare` for the missing guardian
639+
:shares: the collection of `CompensatedDecryptionShare` for the missing guardian from available guardians
740640
:lagrange_coefficients: the lagrange coefficients corresponding to the available guardians that provided shares
741641
"""
742642

743-
contest_shares: Dict[
744-
AVAILABLE_GUARDIAN_ID, CiphertextCompensatedDecryptionContest
745-
] = {
643+
contest_shares: Dict[GUARDIAN_ID, CiphertextCompensatedDecryptionContest] = {
746644
available_guardian_id: compensated_share.contests[contest.object_id]
747645
for available_guardian_id, compensated_share in shares.items()
748646
}
@@ -752,7 +650,7 @@ def reconstruct_decryption_contest(
752650

753651
# collect all of the shares generated for each selection
754652
compensated_selection_shares: Dict[
755-
AVAILABLE_GUARDIAN_ID, CiphertextCompensatedDecryptionSelection
653+
GUARDIAN_ID, CiphertextCompensatedDecryptionSelection
756654
] = {
757655
available_guardian_id: compensated_contest.selections[selection.object_id]
758656
for available_guardian_id, compensated_contest in contest_shares.items()
@@ -782,7 +680,7 @@ def reconstruct_decryption_contest(
782680

783681
def compute_lagrange_coefficients_for_guardians(
784682
available_guardians_keys: List[ElectionPublicKey],
785-
) -> Dict[AVAILABLE_GUARDIAN_ID, ElementModQ]:
683+
) -> Dict[GUARDIAN_ID, ElementModQ]:
786684
"""
787685
Produce all Lagrange coefficients for a collection of available
788686
Guardians, to be used when reconstructing a missing share.

0 commit comments

Comments
 (0)