Skip to content

Commit 05e8034

Browse files
authored
hash now correctly handles integers, other iterables (#379)
* hash now correctly handles integers, other iterables * simplify * adjust assertions to match new hashing reality * cleanup
1 parent f0a25b0 commit 05e8034

File tree

2 files changed

+27
-21
lines changed

2 files changed

+27
-21
lines changed

src/electionguard/hash.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
from abc import abstractmethod
22
from collections.abc import Sequence
33
from hashlib import sha256
4-
from typing import Union, Protocol, runtime_checkable, Sequence as TypedSequence
4+
from typing import (
5+
Iterable,
6+
List,
7+
Union,
8+
Protocol,
9+
runtime_checkable,
10+
Sequence as TypedSequence,
11+
)
512

613
from .group import (
714
ElementModPOrQ,
@@ -69,25 +76,27 @@ def hash_elems(*a: CRYPTO_HASHABLE_ALL) -> ElementModQ:
6976
# that's a bit Python-specific, and we'd rather make it easier for other languages
7077
# to exactly match this hash function.
7178

72-
if not x:
73-
# This case captures empty lists and None, nicely guaranteeing that we don't
74-
# need to do a recursive call if the list is empty. So we need a string to
75-
# feed in for both of these cases. "None" would be a Python-specific thing,
76-
# so we'll go with the more JSON-ish "null".
77-
hash_me = "null"
78-
79-
elif isinstance(x, (ElementModP, ElementModQ)):
79+
if isinstance(x, (ElementModP, ElementModQ)):
8080
hash_me = x.to_hex()
8181
elif isinstance(x, CryptoHashable):
8282
hash_me = x.crypto_hash().to_hex()
8383
elif isinstance(x, str):
84-
# strings are iterable, so it's important to handle them before the following check
84+
# strings are iterable, so it's important to handle them before list-like types
8585
hash_me = x
86-
elif isinstance(x, Sequence):
86+
elif isinstance(x, int):
87+
hash_me = str(x)
88+
elif not x:
89+
# This case captures empty lists and None, nicely guaranteeing that we don't
90+
# need to do a recursive call if the list is empty. So we need a string to
91+
# feed in for both of these cases. "None" would be a Python-specific thing,
92+
# so we'll go with the more JSON-ish "null".
93+
hash_me = "null"
94+
elif isinstance(x, (Sequence, List, Iterable)):
8795
# The simplest way to deal with lists, tuples, and such are to crunch them recursively.
8896
hash_me = hash_elems(*x).to_hex()
8997
else:
9098
hash_me = str(x)
99+
91100
h.update((hash_me + "|").encode("utf-8"))
92101

93102
# We don't need the checked version of int_to_q, because the

tests/property/test_hash.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ def test_basic_hash_properties(self, a: ElementModQ, b: ElementModQ):
2828
if ha != hb:
2929
self.assertNotEqual(a, b)
3030

31-
def test_hash_value_for_zero_string_different_from_zero_number(self):
32-
self.assertNotEqual(hash_elems(0), hash_elems("0"))
31+
def test_hash_for_zero_number_is_zero_string(self):
32+
self.assertEqual(hash_elems(0), hash_elems("0"))
3333

3434
def test_hash_for_non_zero_number_string_same_as_explicit_number(self):
3535
self.assertEqual(hash_elems(1), hash_elems("1"))
@@ -40,27 +40,24 @@ def test_different_strings_casing_not_the_same_hash(self):
4040
hash_elems("welcome to electionguard"),
4141
)
4242

43-
def test_hash_for_empty_string_same_as_null_string(self):
44-
self.assertEqual(hash_elems(""), hash_elems("null"))
45-
4643
def test_hash_for_none_same_as_null_string(self):
4744
self.assertEqual(hash_elems(None), hash_elems("null"))
4845

49-
def test_hash_for_none_same_as_explicit_zero_number(self):
50-
self.assertEqual(hash_elems(None), hash_elems(0))
51-
5246
def test_hash_of_save_values_in_list_are_same_hash(self):
5347
self.assertEqual(hash_elems(["0", "0"]), hash_elems(["0", "0"]))
5448

55-
def test_hash_empty_list_same_as_hash_null_string(self):
49+
def test_hash_null_equivalents(self):
5650
null_list: Optional[List[str]] = None
5751
empty_list: List[str] = []
5852

5953
self.assertEqual(hash_elems(null_list), hash_elems(empty_list))
6054
self.assertEqual(hash_elems(empty_list), hash_elems(None))
61-
self.assertEqual(hash_elems(empty_list), hash_elems(""))
6255
self.assertEqual(hash_elems(empty_list), hash_elems("null"))
6356

57+
def test_hash_not_null_equivalents(self):
58+
self.assertNotEqual(hash_elems(None), hash_elems(""))
59+
self.assertNotEqual(hash_elems(None), hash_elems(0))
60+
6461
def test_hash_value_from_nested_list_and_result_of_hashed_list_by_taking_the_hex(
6562
self,
6663
):

0 commit comments

Comments
 (0)