forked from Election-Tech-Initiative/electionguard-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathballot_compact.py
More file actions
178 lines (148 loc) · 5.47 KB
/
ballot_compact.py
File metadata and controls
178 lines (148 loc) · 5.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
from dataclasses import dataclass
from typing import Dict, List
from .ballot import (
CiphertextBallot,
SubmittedBallot,
PlaintextBallot,
PlaintextBallotContest,
PlaintextBallotSelection,
make_ciphertext_submitted_ballot,
)
from .ballot_box import BallotBoxState
from .election import CiphertextElectionContext
from .election_object_base import sequence_order_sort
from .encrypt import encrypt_ballot_contests
from .group import ElementModQ
from .manifest import (
ContestDescriptionWithPlaceholders,
InternalManifest,
)
from .utils import get_optional
YES_VOTE = 1
NO_VOTE = 0
@dataclass
class CompactPlaintextBallot:
"""A compact plaintext representation of ballot minimized for data size"""
object_id: str
style_id: str
selections: List[bool]
write_ins: Dict[int, str]
@dataclass
class CompactSubmittedBallot:
"""A compact submitted ballot minimized for data size"""
compact_plaintext_ballot: CompactPlaintextBallot
timestamp: int
ballot_nonce: ElementModQ
code_seed: ElementModQ
code: ElementModQ
ballot_box_state: BallotBoxState
def compress_plaintext_ballot(ballot: PlaintextBallot) -> CompactPlaintextBallot:
"""Compress a plaintext ballot into a compact plaintext ballot"""
selections = _get_compact_selections(ballot)
extended_data = _get_compact_write_ins(ballot)
return CompactPlaintextBallot(
ballot.object_id, ballot.style_id, selections, extended_data
)
def compress_submitted_ballot(
ballot: SubmittedBallot,
plaintext_ballot: PlaintextBallot,
ballot_nonce: ElementModQ,
) -> CompactSubmittedBallot:
"""Compress a submitted ballot into a compact submitted ballot"""
return CompactSubmittedBallot(
compress_plaintext_ballot(plaintext_ballot),
ballot.timestamp,
ballot_nonce,
ballot.code_seed,
ballot.code,
ballot.state,
)
def expand_compact_submitted_ballot(
compact_ballot: CompactSubmittedBallot,
internal_manifest: InternalManifest,
context: CiphertextElectionContext,
) -> SubmittedBallot:
"""
Expand a compact submitted ballot using context and
the election manifest into a submitted ballot
"""
# Expand ballot and encrypt & hash contests
plaintext_ballot = expand_compact_plaintext_ballot(
compact_ballot.compact_plaintext_ballot, internal_manifest
)
nonce_seed = CiphertextBallot.nonce_seed(
internal_manifest.manifest_hash,
compact_ballot.compact_plaintext_ballot.object_id,
compact_ballot.ballot_nonce,
)
contests = get_optional(
encrypt_ballot_contests(
plaintext_ballot, internal_manifest, context, nonce_seed
)
)
return make_ciphertext_submitted_ballot(
plaintext_ballot.object_id,
plaintext_ballot.style_id,
internal_manifest.manifest_hash,
compact_ballot.code_seed,
contests,
compact_ballot.code,
compact_ballot.timestamp,
compact_ballot.ballot_box_state,
)
def expand_compact_plaintext_ballot(
compact_ballot: CompactPlaintextBallot, internal_manifest: InternalManifest
) -> PlaintextBallot:
"""Expand a compact plaintext ballot into the original plaintext ballot"""
return PlaintextBallot(
compact_ballot.object_id,
compact_ballot.style_id,
_get_plaintext_contests(compact_ballot, internal_manifest),
)
def _get_compact_selections(ballot: PlaintextBallot) -> List[bool]:
selections = []
for contest in ballot.contests:
for selection in contest.ballot_selections:
selections.append(selection.vote == YES_VOTE)
return selections
def _get_compact_write_ins(ballot: PlaintextBallot) -> Dict[int, str]:
write_ins = {}
index = 0
for contest in ballot.contests:
for selection in contest.ballot_selections:
index += 1
if selection.write_in:
write_ins[index] = selection.write_in
return write_ins
def _get_plaintext_contests(
compact_ballot: CompactPlaintextBallot, internal_manifest: InternalManifest
) -> List[PlaintextBallotContest]:
"""Get ballot contests from compact plaintext ballot"""
index = 0
ballot_style_contests = _get_ballot_style_contests(
compact_ballot.style_id, internal_manifest
)
contests: List[PlaintextBallotContest] = []
for manifest_contest in sequence_order_sort(internal_manifest.contests):
contest_in_style = (
ballot_style_contests.get(manifest_contest.object_id) is not None
)
# Iterate through selections. If contest not in style, mark placeholder
selections: List[PlaintextBallotSelection] = []
for selection in sequence_order_sort(manifest_contest.ballot_selections):
selections.append(
PlaintextBallotSelection(
selection.object_id,
YES_VOTE if compact_ballot.selections[index] else NO_VOTE,
not contest_in_style,
compact_ballot.write_ins.get(index),
)
)
index += 1
contests.append(PlaintextBallotContest(manifest_contest.object_id, selections))
return contests
def _get_ballot_style_contests(
ballot_style_id: str, internal_manifest: InternalManifest
) -> Dict[str, ContestDescriptionWithPlaceholders]:
ballot_style_contests = internal_manifest.get_contests_for(ballot_style_id)
return {contest.object_id: contest for contest in ballot_style_contests}