From b2a568ddd5e872a1338e550a72d30a82f5821af3 Mon Sep 17 00:00:00 2001 From: Yingtong Li Date: Mon, 25 Sep 2017 18:03:43 +1000 Subject: [PATCH] Implement basic crypto --- .gitignore | 1 + build_js.sh | 6 +-- eos/core/bigint/js.py | 39 ++++++++++++++++++ eos/core/bigint/python.py | 45 ++++++++++++++++++--- eos/core/tests.py | 4 ++ eos/js_tests.py | 1 + eos/psgjjr/crypto.py | 84 +++++++++++++++++++++++++++++++++++++++ eos/psgjjr/tests.py | 28 +++++++++++++ 8 files changed, 200 insertions(+), 8 deletions(-) create mode 100644 eos/psgjjr/crypto.py create mode 100644 eos/psgjjr/tests.py diff --git a/.gitignore b/.gitignore index 20acb77..ac938e7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ /venv __javascript__ __pycache__ +refs diff --git a/build_js.sh b/build_js.sh index faa1676..93e399f 100755 --- a/build_js.sh +++ b/build_js.sh @@ -28,11 +28,11 @@ for f in eos.js_tests; do perl -0777 -pi -e "s/__pragma__ \('.*?'\)//gs" eos/__javascript__/$f.js # Transcrypt by default suppresses stack traces for some reason?? - perl -0777 -pi -e "s/__except0__.__cause__ = null;//g" eos/__javascript__/$f.js + perl -0777 -pi -e 's/__except0__.__cause__ = null;//g' eos/__javascript__/$f.js # Disable handling of special attributes - perl -0777 -pi -e "s/var __specialattrib__ = function \(attrib\) \{/var __specialattrib__ = function (attrib) { return false;/g" eos/__javascript__/$f.js + perl -0777 -pi -e 's/var __specialattrib__ = function \(attrib\) \{/var __specialattrib__ = function (attrib) { return false;/g' eos/__javascript__/$f.js # Transcrypt bug - perl -0777 -pi -e "s/EosObject.EosObject/EosObject/g" eos/__javascript__/$f.js + perl -0777 -pi -e 's/property.call \((.*?), \g1.\g1.__impl__(.*?)\)/property.call ($1, $1.__impl__$2)/g' eos/__javascript__/$f.js done diff --git a/eos/core/bigint/js.py b/eos/core/bigint/js.py index 72b9097..9db60fb 100644 --- a/eos/core/bigint/js.py +++ b/eos/core/bigint/js.py @@ -16,6 +16,8 @@ from eos.core.objects import EosObject +import random + # Load jsbn{,2}.js lib = __pragma__('js', ''' (function() {{ @@ -34,6 +36,8 @@ lib = __pragma__('js', ''' class BigInt(EosObject): def __init__(self, a, b=10): + super().__init__() + if isinstance(a, str): self.impl = lib.nbi() self.impl.fromString(a, b) @@ -93,6 +97,41 @@ class BigInt(EosObject): if not isinstance(modulo, BigInt): modulo = BigInt(modulo) return BigInt(self.impl.modPow(other.impl, modulo.impl)) + + def serialise(self): + return str(self) + + @classmethod + def deserialise(cls, value): + return cls(value) + + # Returns a random BigInt from lower_bound to upper_bound, both inclusive + @classmethod + def noncrypto_random(cls, lower_bound, upper_bound): + if not isinstance(lower_bound, cls): + lower_bound = cls(lower_bound) + if not isinstance(upper_bound, cls): + upper_bound = cls(upper_bound) + + bound_range = upper_bound - lower_bound + 1 + bound_range_bits = bound_range.impl.bitLength() + + # Generate a sufficiently large number; work 32 bits at a time + current_range = 0 # bits + max_int = 2 ** 32 - 1 + big_number = cls(0) + while current_range < bound_range_bits: + random_number = cls(random.randint(0, max_int)) + big_number = (big_number << 32) | random_number + current_range = current_range + 32 + + # Since this is the non-crypto version, just do it modulo + return lower_bound + (big_number % bound_range) + + @classmethod + def crypto_random(cls, lower_bound, upper_bound): + # TODO + return cls.noncrypto_random(lower_bound, upper_bound) # TNYI: No native pow() support def pow(a, b, c=None): diff --git a/eos/core/bigint/python.py b/eos/core/bigint/python.py index 356d050..a3ff219 100644 --- a/eos/core/bigint/python.py +++ b/eos/core/bigint/python.py @@ -16,8 +16,13 @@ from eos.core.objects import EosObject +import random +system_random = random.SystemRandom() + class BigInt(EosObject): def __init__(self, a, b=10): + super().__init__() + self.impl = int(a, b) if isinstance(a, str) else int(a) def __pow__(self, other, modulo=None): @@ -28,12 +33,24 @@ class BigInt(EosObject): if not isinstance(modulo, BigInt): modulo = BigInt(modulo) return BigInt(self.impl.__pow__(other.impl, modulo.impl)) + + def serialise(self): + return str(self) + + @classmethod + def deserialise(cls, value): + return cls(value) + + # Returns a random BigInt from lower_bound to upper_bound, both inclusive + @classmethod + def noncrypto_random(cls, lower_bound, upper_bound): + return cls(random.randint(int(lower_bound), int(upper_bound))) + + @classmethod + def crypto_random(cls, lower_bound, upper_bound): + return cls(system_random.randint(int(lower_bound), int(upper_bound))) -for func in [ - '__add__', '__sub__', '__mul__', '__mod__', '__or__', '__lshift__', '__xor__', - '__eq__', '__ne__', '__lt__', '__gt__', '__le__', '__ge__', - '__str__' -]: +for func in ['__add__', '__sub__', '__mul__', '__mod__', '__or__', '__lshift__', '__xor__']: def make_operator_func(func_): # Create a closure def operator_func(self, other): @@ -42,3 +59,21 @@ for func in [ return BigInt(getattr(self.impl, func_)(other.impl)) return operator_func setattr(BigInt, func, make_operator_func(func)) + +for func in ['__eq__', '__ne__', '__lt__', '__gt__', '__le__', '__ge__']: + def make_operator_func(func_): + # Create a closure + def operator_func(self, other): + if not isinstance(other, BigInt): + other = BigInt(other) + return getattr(self.impl, func_)(other.impl) + return operator_func + setattr(BigInt, func, make_operator_func(func)) + +for func in ['__str__', '__int__']: + def make_operator_func(func_): + # Create a closure + def operator_func(self): + return getattr(self.impl, func_)() + return operator_func + setattr(BigInt, func, make_operator_func(func)) diff --git a/eos/core/tests.py b/eos/core/tests.py index 413070d..3f12578 100644 --- a/eos/core/tests.py +++ b/eos/core/tests.py @@ -38,6 +38,10 @@ class EosTestCase: raise Error('Assertion failed: ' + str(a) + ' != ' + str(b)) def assertEqualJSON(self, a, b): + if isinstance(a, EosObject): + a = EosObject.serialise_and_wrap(a) + if isinstance(b, EosObject): + b = EosObject.serialise_and_wrap(b) self.assertEqual(EosObject.to_json(a), EosObject.to_json(b)) def py_only(func): diff --git a/eos/js_tests.py b/eos/js_tests.py index dd04f60..f92ab51 100644 --- a/eos/js_tests.py +++ b/eos/js_tests.py @@ -18,3 +18,4 @@ import eos.js import eos.core.tests import eos.base.tests +import eos.psgjjr.tests diff --git a/eos/psgjjr/crypto.py b/eos/psgjjr/crypto.py new file mode 100644 index 0000000..2bdf8ff --- /dev/null +++ b/eos/psgjjr/crypto.py @@ -0,0 +1,84 @@ +# Eos - Verifiable elections +# Copyright © 2017 RunasSudo (Yingtong Li) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from eos.core.bigint import * +from eos.core.objects import * + +class CyclicGroup(EmbeddedObject): + p = EmbeddedObjectField(BigInt) + g = EmbeddedObjectField(BigInt) + + @property + def q(self): + # p = 2q + 1 + return (self.p - ONE) // TWO + + def random_element(self, crypto_random=True): + crypto_method = BigInt.crypto_random if crypto_random else BigInt.noncrypto_random + return crypto_method(ONE, self.p - ONE) + +# RFC 3526 +DEFAULT_GROUP = CyclicGroup( + p=BigInt('FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF', 16), + g=TWO +) + +class EGPublicKey(EmbeddedObject): + group = EmbeddedObjectField(CyclicGroup) + X = EmbeddedObjectField(BigInt) + + # HAC 8.18 + def encrypt(self, message): + # Choose an element 1 <= k <= p - 2 + k = BigInt.crypto_random(ONE, self.group.p - TWO) + + gamma = pow(self.group.g, k, self.group.p) + delta = (message * pow(self.X, k, self.group.p)) % self.group.p + + return EGCiphertext(public_key=self, gamma=gamma, delta=delta) + +class EGPrivateKey(EmbeddedObject): + public_key = EmbeddedObjectField(EGPublicKey) + x = EmbeddedObjectField(BigInt) + + # HAC 8.17 + @staticmethod + def generate(): + # Choose an element 1 <= x <= p - 2 + x = BigInt.crypto_random(ONE, DEFAULT_GROUP.p - TWO) + # Calculate the public key as G^x + X = pow(DEFAULT_GROUP.g, x, DEFAULT_GROUP.p) + + pk = EGPublicKey(group=DEFAULT_GROUP, X=X) + sk = EGPrivateKey(public_key=pk, x=x) + return sk + + # HAC 8.18 + 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') + + gamma_inv = pow(ciphertext.gamma, self.public_key.group.p - ONE - self.x, self.public_key.group.p) + + return (gamma_inv * ciphertext.delta) % self.public_key.group.p + +class EGCiphertext(EmbeddedObject): + public_key = EmbeddedObjectField(EGPublicKey) + gamma = EmbeddedObjectField(BigInt) # G^k + delta = EmbeddedObjectField(BigInt) # M X^k diff --git a/eos/psgjjr/tests.py b/eos/psgjjr/tests.py new file mode 100644 index 0000000..7b095e1 --- /dev/null +++ b/eos/psgjjr/tests.py @@ -0,0 +1,28 @@ +# Eos - Verifiable elections +# Copyright © 2017 RunasSudo (Yingtong Li) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from eos.core.tests import * + +from eos.core.bigint import * +from eos.psgjjr.crypto import * + +class EGTestCase(EosTestCase): + def test_eg(self): + pt = BigInt.noncrypto_random(ONE, DEFAULT_GROUP.p - ONE) + sk = EGPrivateKey.generate() + ct = sk.public_key.encrypt(pt) + m = sk.decrypt(ct) + self.assertEqualJSON(pt, m)