Skip to content

Commit e5c6633

Browse files
pamtarokeithrfung
andauthored
Feature/hex_hashes (#163)
* Replace uses of big integer representations to just hex where it is used just as a string representation to be hashed. Kept only a couple int uses in P & Q where it was known to be a standard size int (less than 64bits). * fixes format of first '0' in bignum hex getting dropped * lint fixes running `pyenv run black .` * added missing docstring for hex_to_q Co-authored-by: Keith Fung <keithrfung@users.noreply.github.com>
1 parent 682ac7f commit e5c6633

6 files changed

Lines changed: 47 additions & 17 deletions

File tree

src/electionguard/group.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@ class ElementModQ(NamedTuple):
2323

2424
elem: mpz
2525

26+
def to_hex(self) -> str:
27+
"""
28+
Converts from the element to the hex representation of bytes. This is preferable to directly
29+
accessing `elem`, whose representation might change.
30+
"""
31+
h = format(self.elem, "02x")
32+
if len(h) % 2:
33+
h = "0" + h
34+
return h
35+
2636
def to_int(self) -> int:
2737
"""
2838
Converts from the element to a regular integer. This is preferable to directly
@@ -65,6 +75,16 @@ class ElementModP(NamedTuple):
6575

6676
elem: mpz
6777

78+
def to_hex(self) -> str:
79+
"""
80+
Converts from the element to the hex representation of bytes. This is preferable to directly
81+
accessing `elem`, whose representation might change.
82+
"""
83+
h = format(self.elem, "02x")
84+
if len(h) % 2:
85+
h = "0" + h
86+
return h
87+
6888
def to_int(self) -> int:
6989
"""
7090
Converts from the element to a regular integer. This is preferable to directly
@@ -125,6 +145,19 @@ def __str__(self) -> str:
125145
ElementModPorInt = Union[ElementModP, int]
126146

127147

148+
def hex_to_q(input: str) -> Optional[ElementModQ]:
149+
"""
150+
Given a hex string representing bytes, returns an ElementModQ.
151+
Returns `None` if the number is out of the allowed
152+
[0,Q) range.
153+
"""
154+
i = int(input, 16)
155+
if 0 <= i < Q:
156+
return ElementModQ(mpz(i))
157+
else:
158+
return None
159+
160+
128161
def int_to_q(input: Union[str, int]) -> Optional[ElementModQ]:
129162
"""
130163
Given a Python integer, returns an ElementModQ.

src/electionguard/guardian.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from .group import (
88
ElementModP,
99
ElementModQ,
10-
int_to_q,
10+
hex_to_q,
1111
mult_p,
1212
pow_q,
1313
pow_p,
@@ -498,7 +498,7 @@ def compensate_decrypt(
498498
f"compensate decrypt guardian {self.object_id} failed decryption for {missing_guardian_id}"
499499
)
500500
return None
501-
partial_secret_key = get_optional(int_to_q(int(decrypted_value)))
501+
partial_secret_key = get_optional(hex_to_q(decrypted_value))
502502

503503
# 𝑀_{𝑖,l} = 𝐴^P𝑖_{l}
504504
partial_decryption = elgamal.partial_decrypt(partial_secret_key)

src/electionguard/hash.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,15 @@ def hash_elems(*a: CRYPTO_HASHABLE_ALL) -> ElementModQ:
8080
hash_me = "null"
8181

8282
elif isinstance(x, ElementModP) or isinstance(x, ElementModQ):
83-
hash_me = str(x.to_int())
83+
hash_me = x.to_hex()
8484
elif isinstance(x, CryptoHashable):
85-
hash_me = str(x.crypto_hash().to_int())
85+
hash_me = x.crypto_hash().to_hex()
8686
elif isinstance(x, str):
8787
# strings are iterable, so it's important to handle them before the following check
8888
hash_me = x
8989
elif isinstance(x, Sequence):
9090
# The simplest way to deal with lists, tuples, and such are to crunch them recursively.
91-
hash_me = str(hash_elems(*x).to_int())
91+
hash_me = hash_elems(*x).to_hex()
9292
else:
9393
hash_me = str(x)
9494
h.update((hash_me + "|").encode("utf-8"))

src/electionguard/key_ceremony.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
elgamal_combine_public_keys,
2020
elgamal_keypair_random,
2121
)
22-
from .group import int_to_q, rand_q, ElementModP, ElementModQ
22+
from .group import hex_to_q, rand_q, ElementModP, ElementModQ
2323
from .rsa import rsa_keypair, rsa_decrypt, rsa_encrypt
2424
from .schnorr import SchnorrProof, make_schnorr_proof
2525
from .serializable import Serializable
@@ -143,8 +143,7 @@ def generate_elgamal_auxiliary_key_pair() -> AuxiliaryKeyPair:
143143
"""
144144
elgamal_key_pair = elgamal_keypair_random()
145145
return AuxiliaryKeyPair(
146-
str(elgamal_key_pair.secret_key.to_int()),
147-
str(elgamal_key_pair.public_key.to_int()),
146+
elgamal_key_pair.secret_key.to_hex(), elgamal_key_pair.public_key.to_hex(),
148147
)
149148

150149

@@ -190,7 +189,7 @@ def generate_election_partial_key_backup(
190189
value = compute_polynomial_coordinate(
191190
auxiliary_public_key.sequence_order, polynomial
192191
)
193-
encrypted_value = encrypt(str(value.to_int()), auxiliary_public_key.key)
192+
encrypted_value = encrypt(value.to_hex(), auxiliary_public_key.key)
194193
if encrypted_value is None:
195194
return None
196195
return ElectionPartialKeyBackup(
@@ -240,7 +239,7 @@ def verify_election_partial_key_backup(
240239
return ElectionPartialKeyVerification(
241240
backup.owner_id, backup.designated_id, verifier_id, False
242241
)
243-
value = get_optional(int_to_q(int(decrypted_value)))
242+
value = get_optional(hex_to_q(decrypted_value))
244243
return ElectionPartialKeyVerification(
245244
backup.owner_id,
246245
backup.designated_id,

src/electionguard/rsa.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,9 @@ def rsa_encrypt(message: str, public_key: str) -> Optional[str]:
6868
"""
6969
data = bytes(public_key, ISO_ENCODING)
7070
rsa_public_key: RSAPublicKey = load_pem_public_key(data, backend=default_backend())
71-
integer = int(message)
72-
bits = count_set_bits(integer)
73-
if bits > MAX_BITS:
71+
plaintext = bytes.fromhex(message)
72+
if len(plaintext) > MAX_BITS:
7473
return None
75-
plaintext = integer.to_bytes(bits, BYTE_ORDER)
7674
ciphertext = rsa_public_key.encrypt(plaintext, PKCS1v15())
7775
return str(ciphertext, ISO_ENCODING)
7876

@@ -95,8 +93,8 @@ def rsa_decrypt(encrypted_message: str, private_key: str) -> Optional[str]:
9593
plaintext = rsa_private_key.decrypt(ciphertext, PKCS1v15())
9694
except ValueError:
9795
return None
98-
integer = int.from_bytes(plaintext, BYTE_ORDER)
99-
return str(integer)
96+
hex_str = plaintext.hex()
97+
return hex_str
10098

10199

102100
def count_set_bits(n: int) -> int:

tests/test_rsa.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
class TestRSA(TestCase):
77
def test_rsa_encrypt(self) -> None:
88
# Arrange
9-
message = "1118632206964768372384343373859700232583178373031391293942056347262996938448167273037401292830794700541756937515976417908858473208686448118280677278101719098670646913045584007219899471676906742553167177135624664615778816843133781654175330682468454244343379"
9+
message = "9893e1c926521dc595d501056d03c4387b87986089539349bed6eb1018229b2e0029dd38647bfc80746726b3710c8ac3f69187da2234b438370a4348a784791813b9857446eb14afc676eece5b789a207bcf633ba1676d3410913ae46dd247166c6a682cb0ccc5ecde53"
1010

1111
# Act
1212
key_pair = rsa_keypair()

0 commit comments

Comments
 (0)