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)