Skip to content

Commit 404522b

Browse files
Handle dates consistently (#366)
* Handle dates consistently add hash tests * use poetry to run mkdocs * remove frozen attributes https://bugs.python.org/issue43176 * extend test deadline
1 parent a5232f1 commit 404522b

13 files changed

Lines changed: 196 additions & 46 deletions

File tree

Makefile

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ all: environment install build validate auto-lint coverage
1111
environment:
1212
@echo 🔧 ENVIRONMENT SETUP
1313
make install-gmp
14-
make install-poetry
15-
make install-mkdocs
14+
#make install-poetry
15+
#make install-mkdocs
16+
pip install 'poetry==1.0.10'
17+
poetry install
1618
@echo 🚨 Be sure to add poetry to PATH
1719

1820
install:
@@ -83,7 +85,7 @@ lint:
8385
poetry build
8486
poetry run twine check dist/*
8587
@echo 5.Documentation
86-
mkdocs build --strict
88+
poetry run mkdocs build --strict
8789

8890
auto-lint:
8991
@echo 💚 AUTO LINT

src/electionguard/chaum_pedersen.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from .proof import Proof, ProofUsage
2222

2323

24-
@dataclass(frozen=True)
24+
@dataclass
2525
class DisjunctiveChaumPedersenProof(Proof):
2626
"""
2727
Representation of disjunctive Chaum Pederson proof
@@ -137,7 +137,7 @@ def is_valid(
137137
return success
138138

139139

140-
@dataclass(frozen=True)
140+
@dataclass
141141
class ChaumPedersenProof(Proof):
142142
"""
143143
Representation of a generic Chaum-Pedersen Zero Knowledge proof
@@ -253,7 +253,7 @@ def is_valid(
253253
return success
254254

255255

256-
@dataclass(frozen=True)
256+
@dataclass
257257
class ConstantChaumPedersenProof(Proof):
258258
"""
259259
Representation of constant Chaum Pederson proof

src/electionguard/election.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
from dataclasses import dataclass
22

3-
from .group import Q, P, R, G, ElementModQ, ElementModP
3+
from .group import (
4+
Q,
5+
P,
6+
R,
7+
G,
8+
ElementModQ,
9+
ElementModP,
10+
int_to_p_unchecked,
11+
int_to_q_unchecked,
12+
)
413
from .hash import hash_elems
514
from .serializable import Serializable
615

@@ -98,7 +107,12 @@ def make_ciphertext_election_context(
98107
# form the basis of subsequent hash computations.
99108

100109
crypto_base_hash = hash_elems(
101-
P, Q, G, number_of_guardians, quorum, description_hash
110+
int_to_p_unchecked(P),
111+
int_to_q_unchecked(Q),
112+
int_to_p_unchecked(G),
113+
number_of_guardians,
114+
quorum,
115+
description_hash,
102116
)
103117
crypto_extended_base_hash = hash_elems(crypto_base_hash, commitment_hash)
104118
return CiphertextElectionContext(

src/electionguard/elgamal.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@
1010
pow_p,
1111
ZERO_MOD_Q,
1212
TWO_MOD_Q,
13-
int_to_q,
1413
rand_range_q,
14+
int_to_q_unchecked,
1515
)
1616
from .hash import hash_elems
17-
from .logs import log_error
18-
from .utils import flatmap_optional, get_optional
17+
from .logs import log_info, log_error
18+
from .utils import get_optional
1919

2020
ELGAMAL_SECRET_KEY = ElementModQ
2121
ELGAMAL_PUBLIC_KEY = ElementModP
@@ -128,18 +128,22 @@ def elgamal_encrypt(
128128
:param m: Message to elgamal_encrypt; must be an integer in [0,Q).
129129
:param nonce: Randomly chosen nonce in [1,Q).
130130
:param public_key: ElGamal public key.
131-
:return: A ciphertext tuple.
131+
:return: An `ElGamalCiphertext`.
132132
"""
133133
if nonce == ZERO_MOD_Q:
134134
log_error("ElGamal encryption requires a non-zero nonce")
135135
return None
136136

137-
return flatmap_optional(
138-
int_to_q(m),
139-
lambda e: ElGamalCiphertext(
140-
g_pow_p(nonce), mult_p(g_pow_p(e), pow_p(public_key, nonce))
141-
),
142-
)
137+
pad = g_pow_p(nonce)
138+
gpowp_m = g_pow_p(int_to_q_unchecked(m))
139+
pubkey_pow_n = pow_p(public_key, nonce)
140+
data = mult_p(gpowp_m, pubkey_pow_n)
141+
142+
log_info(f": publicKey: {public_key.to_hex()}")
143+
log_info(f": pad: {pad.to_hex()}")
144+
log_info(f": data: {data.to_hex()}")
145+
146+
return ElGamalCiphertext(pad, data)
143147

144148

145149
def elgamal_add(*ciphertexts: ElGamalCiphertext) -> ElGamalCiphertext:

src/electionguard/encrypt.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from datetime import datetime
12
from typing import List, Optional
23
from uuid import getnode
34

@@ -17,7 +18,7 @@
1718
from .election import CiphertextElectionContext
1819
from .elgamal import elgamal_encrypt
1920
from .group import ElementModP, ElementModQ, rand_q
20-
from .logs import log_warning
21+
from .logs import log_info, log_warning
2122
from .manifest import (
2223
InternalManifest,
2324
ContestDescription,
@@ -58,6 +59,8 @@ def __init__(
5859
self.launch_code = launch_code
5960
self.location = location
6061

62+
log_info(f": EncryptionDevice: Created: UUID: {uuid} at: {location}")
63+
6164
def get_hash(self) -> ElementModQ:
6265
"""
6366
Get hash for encryption device
@@ -67,8 +70,12 @@ def get_hash(self) -> ElementModQ:
6770
self.uuid, self.session_id, self.launch_code, self.location
6871
)
6972

73+
# pylint: disable=no-self-use
7074
def get_timestamp(self) -> int:
71-
pass
75+
"""
76+
Get the current timestamp in utc
77+
"""
78+
return int(datetime.utcnow().timestamp())
7279

7380

7481
class EncryptionMediator:
@@ -79,7 +86,7 @@ class EncryptionMediator:
7986
"""
8087

8188
_internal_manifest: InternalManifest
82-
_encryption: CiphertextElectionContext
89+
_context: CiphertextElectionContext
8390
_encryption_seed: ElementModQ
8491

8592
def __init__(
@@ -89,15 +96,17 @@ def __init__(
8996
encryption_device: EncryptionDevice,
9097
):
9198
self._internal_manifest = internal_manifest
92-
self._encryption = context
99+
self._context = context
93100
self._encryption_seed = encryption_device.get_hash()
94101

95102
def encrypt(self, ballot: PlaintextBallot) -> Optional[CiphertextBallot]:
96103
"""
97104
Encrypt the specified ballot using the cached election context.
98105
"""
106+
107+
log_info(f" encrypt: objectId: {ballot.object_id}")
99108
encrypted_ballot = encrypt_ballot(
100-
ballot, self._internal_manifest, self._encryption, self._encryption_seed
109+
ballot, self._internal_manifest, self._context, self._encryption_seed
101110
)
102111
if encrypted_ballot is not None and encrypted_ballot.code is not None:
103112
self._encryption_seed = encrypted_ballot.code
@@ -185,6 +194,10 @@ def encrypt_selection(
185194
selection_nonce = nonce_sequence[selection_description.sequence_order]
186195
disjunctive_chaum_pedersen_nonce = next(iter(nonce_sequence))
187196

197+
log_info(
198+
f": encrypt_selection: for {selection_description.object_id} hash: {selection_description_hash.to_hex()}"
199+
)
200+
188201
selection_representation = selection.vote
189202

190203
# Generate the encryption
@@ -442,6 +455,9 @@ def encrypt_ballot(
442455
random_master_nonce,
443456
)
444457

458+
log_info(f": manifest_hash : {internal_manifest.manifest_hash.to_hex()}")
459+
log_info(f": encryption_seed : {encryption_seed.to_hex()}")
460+
445461
encrypted_contests = encrypt_ballot_contests(
446462
ballot, internal_manifest, context, nonce_seed
447463
)

src/electionguard/guardian.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ def publish_guardian_record(election_public_key: ElectionPublicKey) -> GuardianR
9797
)
9898

9999

100-
@dataclass(frozen=True)
100+
@dataclass
101101
class PrivateGuardianRecord(Serializable):
102102
"""Unpublishable private record containing information per Guardian"""
103103

src/electionguard/logs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ def get_stream_handler() -> logging.StreamHandler:
9898
Get a Stream Handler, sends only warnings and errors to stdout.
9999
"""
100100
stream_handler = logging.StreamHandler(sys.stdout)
101-
stream_handler.setLevel(logging.WARNING)
101+
stream_handler.setLevel(logging.INFO)
102102
stream_handler.setFormatter(logging.Formatter(FORMAT))
103103
return stream_handler
104104

src/electionguard/manifest.py

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
from .election_object_base import ElectionObjectBase
88
from .group import ElementModQ
99
from .hash import CryptoHashable, hash_elems
10-
from .logs import log_warning
10+
from .logs import log_warning, log_debug
1111
from .serializable import Serializable
12-
from .utils import get_optional, to_ticks
12+
from .utils import get_optional, to_iso_date_string
1313

1414

1515
@unique
@@ -103,7 +103,9 @@ def crypto_hash(self) -> ElementModQ:
103103
"""
104104
A hash representation of the object
105105
"""
106-
return hash_elems(self.annotation, self.value)
106+
hash = hash_elems(self.annotation, self.value)
107+
log_debug(f"{self.__class__} : crypto_hash: {hash.to_hex()}")
108+
return hash
107109

108110

109111
@dataclass(eq=True, unsafe_hash=True)
@@ -120,7 +122,9 @@ def crypto_hash(self) -> ElementModQ:
120122
"""
121123
A hash representation of the object
122124
"""
123-
return hash_elems(self.value, self.language)
125+
hash = hash_elems(self.value, self.language)
126+
log_debug(f"{self.__class__} : crypto_hash: {hash.to_hex()}")
127+
return hash
124128

125129

126130
@dataclass(eq=True, unsafe_hash=True)
@@ -136,7 +140,9 @@ def crypto_hash(self) -> ElementModQ:
136140
"""
137141
A hash representation of the object
138142
"""
139-
return hash_elems(self.text)
143+
hash = hash_elems(self.text)
144+
log_debug(f"{self.__class__} : crypto_hash: {hash.to_hex()}")
145+
return hash
140146

141147

142148
@dataclass(eq=True, unsafe_hash=True)
@@ -155,7 +161,9 @@ def crypto_hash(self) -> ElementModQ:
155161
"""
156162
A hash representation of the object
157163
"""
158-
return hash_elems(self.name, self.address_line, self.email, self.phone)
164+
hash = hash_elems(self.name, self.address_line, self.email, self.phone)
165+
log_debug(f"{self.__class__} : crypto_hash: {hash.to_hex()}")
166+
return hash
159167

160168

161169
@dataclass(eq=True, unsafe_hash=True)
@@ -174,9 +182,11 @@ def crypto_hash(self) -> ElementModQ:
174182
"""
175183
A hash representation of the object
176184
"""
177-
return hash_elems(
185+
hash = hash_elems(
178186
self.object_id, self.name, str(self.type.name), self.contact_information
179187
)
188+
log_debug(f"{self.__class__} : crypto_hash: {hash.to_hex()}")
189+
return hash
180190

181191

182192
@dataclass(eq=True, unsafe_hash=True)
@@ -193,9 +203,11 @@ def crypto_hash(self) -> ElementModQ:
193203
"""
194204
A hash representation of the object
195205
"""
196-
return hash_elems(
206+
hash = hash_elems(
197207
self.object_id, self.geopolitical_unit_ids, self.party_ids, self.image_uri
198208
)
209+
log_debug(f"{self.__class__} : crypto_hash: {hash.to_hex()}")
210+
return hash
199211

200212

201213
@dataclass(eq=True, unsafe_hash=True)
@@ -220,13 +232,15 @@ def crypto_hash(self) -> ElementModQ:
220232
"""
221233
A hash representation of the object
222234
"""
223-
return hash_elems(
235+
hash = hash_elems(
224236
self.object_id,
225237
self.name,
226238
self.abbreviation,
227239
self.color,
228240
self.logo_uri,
229241
)
242+
log_debug(f"{self.__class__} : crypto_hash: {hash.to_hex()}")
243+
return hash
230244

231245

232246
@dataclass(eq=True, unsafe_hash=True)
@@ -256,7 +270,9 @@ def crypto_hash(self) -> ElementModQ:
256270
"""
257271
A hash representation of the object
258272
"""
259-
return hash_elems(self.object_id, self.name, self.party_id, self.image_uri)
273+
hash = hash_elems(self.object_id, self.name, self.party_id, self.image_uri)
274+
log_debug(f"{self.__class__} : crypto_hash: {hash.to_hex()}")
275+
return hash
260276

261277

262278
@dataclass(eq=True, unsafe_hash=True)
@@ -287,7 +303,9 @@ def crypto_hash(self) -> ElementModQ:
287303
"""
288304
A hash representation of the object
289305
"""
290-
return hash_elems(self.object_id, self.sequence_order, self.candidate_id)
306+
hash = hash_elems(self.object_id, self.sequence_order, self.candidate_id)
307+
log_debug(f"{self.__class__} : crypto_hash: {hash.to_hex()}")
308+
return hash
291309

292310

293311
# pylint: disable=too-many-instance-attributes
@@ -358,7 +376,7 @@ def crypto_hash(self) -> ElementModQ:
358376
description match up.
359377
"""
360378
# remove any placeholders from the hash mechanism
361-
return hash_elems(
379+
hash = hash_elems(
362380
self.object_id,
363381
self.sequence_order,
364382
self.electoral_district_id,
@@ -370,6 +388,8 @@ def crypto_hash(self) -> ElementModQ:
370388
self.votes_allowed,
371389
self.ballot_selections,
372390
)
391+
log_debug(f"{self.__class__} : crypto_hash: {hash.to_hex()}")
392+
return hash
373393

374394
def is_valid(self) -> bool:
375395
"""
@@ -561,19 +581,20 @@ def crypto_hash(self) -> ElementModQ:
561581
"""
562582
Returns a hash of the metadata components of the election
563583
"""
564-
565-
return hash_elems(
584+
hash = hash_elems(
566585
self.election_scope_id,
567586
str(self.type.name),
568-
to_ticks(self.start_date),
569-
to_ticks(self.end_date),
587+
to_iso_date_string(self.start_date),
588+
to_iso_date_string(self.end_date),
570589
self.name,
571590
self.contact_information,
572591
self.geopolitical_units,
573592
self.parties,
574593
self.contests,
575594
self.ballot_styles,
576595
)
596+
log_debug(f"{self.__class__} : crypto_hash: {hash.to_hex()}")
597+
return hash
577598

578599
def is_valid(self) -> bool:
579600
"""

0 commit comments

Comments
 (0)