From 2ec44db65d4f067951a251461efe28b112e7a1d2 Mon Sep 17 00:00:00 2001 From: Yingtong Li Date: Wed, 27 Sep 2017 18:09:15 +1000 Subject: [PATCH] Implement signed ElGamal encryption --- eos/core/objects/__init__.py | 16 +++++---- eos/core/tests.py | 2 +- eos/js.py | 7 +++- eos/js_tests.py | 2 +- eos/{psgjjr => psr}/bitstream.py | 0 eos/{psgjjr => psr}/crypto.py | 59 +++++++++++++++++++++++++++++--- eos/{psgjjr => psr}/tests.py | 12 +++++-- 7 files changed, 83 insertions(+), 15 deletions(-) rename eos/{psgjjr => psr}/bitstream.py (100%) rename eos/{psgjjr => psr}/crypto.py (63%) rename eos/{psgjjr => psr}/tests.py (92%) diff --git a/eos/core/objects/__init__.py b/eos/core/objects/__init__.py index f10f5fc..c6fa8b7 100644 --- a/eos/core/objects/__init__.py +++ b/eos/core/objects/__init__.py @@ -150,7 +150,7 @@ class EosObject(metaclass=EosObjectType): @property def hash(self): - return EosObject.to_sha256(EosObject.serialise_and_wrap(self)) + return EosObject.to_sha256(EosObject.to_json(EosObject.serialise_and_wrap(self)))[0] @staticmethod def serialise_and_wrap(value, object_type=None): @@ -179,16 +179,20 @@ class EosObject(metaclass=EosObjectType): return JSON.parse(value) @staticmethod - def to_sha256(value): + def to_sha256(*values): + from eos.core.bigint import BigInt + if is_python: sha_obj = hashlib.sha256() - sha_obj.update(value.encode('utf-8')) - return base64.b64encode(sha_obj.digest()).decode('utf-8') + for value in values: + sha_obj.update(value.encode('utf-8')) + return base64.b64encode(sha_obj.digest()).decode('utf-8'), BigInt(sha_obj.hexdigest(), 16) else: # TNYI: This is completely borked sha_obj = __pragma__('js', '{}', 'new lib.jsSHA("SHA-256", "TEXT")') - sha_obj.js_update(value) - return sha_obj.getHash('B64') + for value in values: + sha_obj.js_update(value) + return sha_obj.getHash('B64'), BigInt(sha_obj.getHash('HEX'), 16) class EosList(EosObject): def __init__(self, *args): diff --git a/eos/core/tests.py b/eos/core/tests.py index 735b6a1..1658cfe 100644 --- a/eos/core/tests.py +++ b/eos/core/tests.py @@ -95,7 +95,7 @@ class ObjectTestCase(EosTestCase): class HashTestCase(EosTestCase): def test_hash(self): - self.assertEqual(EosObject.to_sha256('Hello World!'), 'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=') + self.assertEqual(EosObject.to_sha256('Hello World!')[0], 'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=') class BigIntTestCase(EosTestCase): def test_basic(self): diff --git a/eos/js.py b/eos/js.py index 279f0d6..bfe188c 100644 --- a/eos/js.py +++ b/eos/js.py @@ -16,4 +16,9 @@ import eos.core.objects import eos.core.bigint -import eos.core.bitstring + +import eos.base.election +import eos.base.workflow + +import eos.psr.bitstream +import eos.psr.crypto diff --git a/eos/js_tests.py b/eos/js_tests.py index f92ab51..8ebd141 100644 --- a/eos/js_tests.py +++ b/eos/js_tests.py @@ -18,4 +18,4 @@ import eos.js import eos.core.tests import eos.base.tests -import eos.psgjjr.tests +import eos.psr.tests diff --git a/eos/psgjjr/bitstream.py b/eos/psr/bitstream.py similarity index 100% rename from eos/psgjjr/bitstream.py rename to eos/psr/bitstream.py diff --git a/eos/psgjjr/crypto.py b/eos/psr/crypto.py similarity index 63% rename from eos/psgjjr/crypto.py rename to eos/psr/crypto.py index c3f4e24..62ff3f5 100644 --- a/eos/psgjjr/crypto.py +++ b/eos/psr/crypto.py @@ -58,19 +58,21 @@ class EGPublicKey(EmbeddedObject): return EGCiphertext(public_key=self, gamma=gamma, delta=delta) class EGPrivateKey(EmbeddedObject): + pk_class = EGPublicKey + public_key = EmbeddedObjectField(EGPublicKey) x = EmbeddedObjectField(BigInt) # HAC 8.17 - @staticmethod - def generate(group=DEFAULT_GROUP): + @classmethod + def generate(cls, group=DEFAULT_GROUP): # Choose an element 1 <= x <= p - 2 x = BigInt.crypto_random(ONE, group.p - TWO) # Calculate the public key as G^x X = pow(group.g, x, group.p) - pk = EGPublicKey(group=group, X=X) - sk = EGPrivateKey(public_key=pk, x=x) + pk = cls.pk_class(group=group, X=X) + sk = cls(public_key=pk, x=x) return sk # HAC 8.18 @@ -90,3 +92,52 @@ class EGCiphertext(EmbeddedObject): public_key = EmbeddedObjectField(EGPublicKey) gamma = EmbeddedObjectField(BigInt) # G^k delta = EmbeddedObjectField(BigInt) # M X^k + +# Signed ElGamal per Schnorr & Jakobssen +class SEGPublicKey(EGPublicKey): + def encrypt(self, message): + message += ONE # Dodgy hack to allow zeroes + + if message <= ZERO: + raise Exception('Invalid message') + if message >= self.group.p: + raise Exception('Invalid message') + + # Choose an element 1 <= k <= p - 2 + r = BigInt.crypto_random(ONE, self.group.p - TWO) + s = BigInt.crypto_random(ONE, self.group.p - TWO) + + gamma = pow(self.group.g, r, self.group.p) # h + delta = (message * pow(self.X, r, self.group.p)) % self.group.p # f + + _, c = EosObject.to_sha256(str(pow(self.group.g, s, self.group.p)), str(gamma), str(delta)) + + z = s + c*r + + return SEGCiphertext(public_key=self, gamma=gamma, delta=delta, c=c, z=z) + +class SEGPrivateKey(EGPrivateKey): + pk_class = SEGPublicKey + + def decrypt(self, ciphertext): + if ( + ciphertext.gamma <= ZERO or ciphertext.gamma >= self.public_key.group.p or + ciphertext.delta <= ZERO or ciphertext.delta >= self.public_key.group.p + ): + raise Exception('Ciphertext is malformed') + + gs = (pow(self.public_key.group.g, ciphertext.z, self.public_key.group.p) * pow(ciphertext.gamma, self.public_key.group.p - ONE - ciphertext.c, self.public_key.group.p)) % self.public_key.group.p + _, c = EosObject.to_sha256(str(gs), str(ciphertext.gamma), str(ciphertext.delta)) + + if ciphertext.c != c: + raise Exception('Signature is invalid') + + gamma_inv = pow(ciphertext.gamma, self.public_key.group.p - ONE - self.x, self.public_key.group.p) + + pt = (gamma_inv * ciphertext.delta) % self.public_key.group.p + return pt - ONE + +class SEGCiphertext(EGCiphertext): + public_key = EmbeddedObjectField(SEGPublicKey) + c = EmbeddedObjectField(BigInt) + z = EmbeddedObjectField(BigInt) diff --git a/eos/psgjjr/tests.py b/eos/psr/tests.py similarity index 92% rename from eos/psgjjr/tests.py rename to eos/psr/tests.py index 6fb43d6..112c14a 100644 --- a/eos/psgjjr/tests.py +++ b/eos/psr/tests.py @@ -17,8 +17,8 @@ from eos.core.tests import * from eos.core.bigint import * -from eos.psgjjr.bitstream import * -from eos.psgjjr.crypto import * +from eos.psr.bitstream import * +from eos.psr.crypto import * class EGTestCase(EosTestCase): def test_eg(self): @@ -28,6 +28,14 @@ class EGTestCase(EosTestCase): m = sk.decrypt(ct) self.assertEqualJSON(pt, m) +class SEGTestCase(EosTestCase): + def test_eg(self): + pt = DEFAULT_GROUP.random_element() + sk = SEGPrivateKey.generate() + ct = sk.public_key.encrypt(pt) + m = sk.decrypt(ct) + self.assertEqualJSON(pt, m) + class BitStreamTestCase(EosTestCase): def test_bitstream(self): bs = BitStream(BigInt('100101011011', 2))