From db74f67474fd801791f9cc5d501dd3e70cf68538 Mon Sep 17 00:00:00 2001 From: RunasSudo Date: Thu, 5 Oct 2017 20:26:40 +1100 Subject: [PATCH] Correctly map messages to G_q instead of simply using Z_p* --- build_js.sh | 3 ++ eos/core/bigint/js.py | 13 +++++++++ eos/core/bigint/python.py | 14 ++++++++- eos/core/tests.py | 7 +++++ eos/psr/crypto.py | 55 ++++++++++++++++++++++++++--------- eos/psr/election.py | 8 ++++-- eos/psr/mixnet.py | 2 +- eos/psr/secretsharing.py | 8 ++---- eos/psr/tests.py | 60 ++++++++++++++++++++++++++++++++------- 9 files changed, 135 insertions(+), 35 deletions(-) diff --git a/build_js.sh b/build_js.sh index 93e399f..b399ac5 100755 --- a/build_js.sh +++ b/build_js.sh @@ -33,6 +33,9 @@ for f in eos.js_tests; do # 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 + # Fix handling of properties + perl -077 -pi -e 's/var __get__ = function \(self, func, quotedFuncName\) \{/var __get__ = function (self, func, quotedFuncName) { if(typeof(func) != "function"){return func;}/g' eos/__javascript__/$f.js + # Transcrypt bug 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 c436a44..fd286e5 100644 --- a/eos/core/bigint/js.py +++ b/eos/core/bigint/js.py @@ -55,6 +55,9 @@ class BigInt(EosObject): ('__add__', 'add'), ('__sub__', 'subtract'), ('__mul__', 'multiply'), + ('__floordiv__', 'divide'), + ('__truediv__', 'divide'), + ('__div__', 'divide'), # TNYI: Still uses Python 2... ('__mod__', 'mod'), ('__and__', 'and'), ('__or__', 'or'), @@ -87,6 +90,16 @@ class BigInt(EosObject): return func_(self.impl.compareTo(other.impl)) return operator_func setattr(self, key, make_operator_func(func)) + + for key, func in [ + ('__neg__', 'negate') + ]: + def make_operator_func(func_): + # Create a closure + def operator_func(): + return BigInt((getattr(self.impl, func_).bind(self.impl))()) + return operator_func + setattr(self, key, make_operator_func(func)) def __str__(self): return str(self.impl) diff --git a/eos/core/bigint/python.py b/eos/core/bigint/python.py index 3882bf0..6c5e36a 100644 --- a/eos/core/bigint/python.py +++ b/eos/core/bigint/python.py @@ -39,6 +39,10 @@ class BigInt(EosObject): modulo = BigInt(modulo) return BigInt(self.impl.__pow__(other.impl, modulo.impl)) + def __truediv__(self, other): + # Python will try to compute this as a float + return self.__floordiv__(other) + def nbits(self): return math.ceil(math.log2(self.impl)) if self.impl > 0 else 0 @@ -58,7 +62,7 @@ class BigInt(EosObject): 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__', '__and__', '__or__', '__lshift__', '__rshift__', '__xor__']: +for func in ['__add__', '__sub__', '__mul__', '__floordiv__', '__mod__', '__and__', '__or__', '__lshift__', '__rshift__', '__xor__']: def make_operator_func(func_): # Create a closure def operator_func(self, other): @@ -78,6 +82,14 @@ for func in ['__eq__', '__ne__', '__lt__', '__gt__', '__le__', '__ge__']: return operator_func setattr(BigInt, func, make_operator_func(func)) +for func in ['__neg__']: + def make_operator_func(func_): + # Create a closure + def operator_func(self): + return BigInt(getattr(self.impl, func_)()) + return operator_func + setattr(BigInt, func, make_operator_func(func)) + for func in ['__str__', '__int__']: def make_operator_func(func_): # Create a closure diff --git a/eos/core/tests.py b/eos/core/tests.py index 5d8490f..b44dd2b 100644 --- a/eos/core/tests.py +++ b/eos/core/tests.py @@ -33,6 +33,13 @@ class EosTestCase: if not a: raise Error('Assertion failed: ' + str(a) + ' not True') + def assertFalse(self, a): + if is_python: + self.impl.assertFalse(a) + else: + if not a: + raise Error('Assertion failed: ' + str(a) + ' not False') + def assertEqual(self, a, b): if is_python: self.impl.assertEqual(a, b) diff --git a/eos/psr/crypto.py b/eos/psr/crypto.py index cc958ae..685596e 100644 --- a/eos/psr/crypto.py +++ b/eos/psr/crypto.py @@ -26,11 +26,20 @@ class CyclicGroup(EmbeddedObject): @property def q(self): # p = 2q + 1 - return (self.p - ONE) // TWO + return (self.p - ONE) / TWO - def random_element(self, crypto_random=True): + def random_Zp_element(self, crypto_random=True): crypto_method = BigInt.crypto_random if crypto_random else BigInt.noncrypto_random return crypto_method(ONE, self.p - ONE) + + def random_Zps_element(self, crypto_random=True): + crypto_method = BigInt.crypto_random if crypto_random else BigInt.noncrypto_random + # Z_p* = {1..p-1} provided that p is a prime + return crypto_method(ONE, self.p - ONE) + + def random_Zq_element(self, crypto_random=True): + crypto_method = BigInt.crypto_random if crypto_random else BigInt.noncrypto_random + return crypto_method(ZERO, self.q - ONE) # RFC 3526 DEFAULT_GROUP = CyclicGroup( @@ -42,10 +51,12 @@ class EGPublicKey(EmbeddedObject): group = EmbeddedObjectField(CyclicGroup) X = EmbeddedObjectField(BigInt) + def nbits(self): + # Our messages are restricted to G_q + return self.group.q.nbits() - 1 + # HAC 8.18 - def encrypt(self, message): - message += ONE # Dodgy hack to allow zeroes - + def _encrypt(self, message): if message <= ZERO: raise Exception('Invalid message') if message >= self.group.p: @@ -58,6 +69,23 @@ class EGPublicKey(EmbeddedObject): delta = (message * pow(self.X, k, self.group.p)) % self.group.p return EGCiphertext(public_key=self, gamma=gamma, delta=delta) + + # Adida 2008 + def encrypt(self, message): + if message < ZERO: + raise Exception('Invalid message') + if message >= self.group.q: + raise Exception('Invalid message') + + m0 = message + ONE + + if pow(m0, self.group.q, self.group.p) == ONE: + # m0 is already in G_q + return self._encrypt(m0) + else: + # For the life of me I can't find any reputable references for this aside from Adida 2008... + m0 = (-m0) % self.group.p + return self._encrypt(m0) class EGPrivateKey(EmbeddedObject): pk_class = EGPublicKey @@ -88,7 +116,12 @@ class EGPrivateKey(EmbeddedObject): 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 + + # Undo the encryption mapping + if pt < self.public_key.group.q: + return pt - ONE + else: + return ((-pt) % self.public_key.group.p) - ONE class EGCiphertext(EmbeddedObject): public_key = EmbeddedObjectField(EGPublicKey) @@ -106,14 +139,7 @@ class EGCiphertext(EmbeddedObject): # 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') - + def _encrypt(self, 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) @@ -163,6 +189,7 @@ class PedersenVSSPrivateKey(EmbeddedObject): def get_modified_secret(self): mod_s = self.x for j in range(1, threshold + 1): # 1 to threshold + ... def decrypt(self, ciphertext): if ( diff --git a/eos/psr/election.py b/eos/psr/election.py index 48876ef..1b4fdb1 100644 --- a/eos/psr/election.py +++ b/eos/psr/election.py @@ -29,8 +29,8 @@ class BlockEncryptedAnswer(EncryptedAnswer): pt = EosObject.to_json(EosObject.serialise_and_wrap(obj)) bs = BitStream() bs.write_string(pt) - bs.multiple_of(pk.group.p.nbits() - 1, True) - ct = bs.map(pk.encrypt, pk.group.p.nbits() - 1) + bs.multiple_of(pk.nbits(), True) + ct = bs.map(pk.encrypt, pk.nbits()) return cls(blocks=ct) @@ -38,7 +38,7 @@ class BlockEncryptedAnswer(EncryptedAnswer): if sk is None: sk = self.recurse_parents(PSRElection).sk - bs = BitStream.unmap(self.blocks, sk.decrypt, sk.public_key.group.p.nbits() - 1) + bs = BitStream.unmap(self.blocks, sk.decrypt, sk.public_key.nbits()) m = bs.read_string() obj = EosObject.deserialise_and_unwrap(EosObject.from_json(m)) @@ -155,4 +155,6 @@ class PSRElection(Election): _db_name = Election._name sk = EmbeddedObjectField(SEGPrivateKey) # TODO: Threshold + + public_key = EmbeddedObjectField(SEGPublicKey) mixing_trustees = EmbeddedObjectListField(MixingTrustee) diff --git a/eos/psr/mixnet.py b/eos/psr/mixnet.py index d81e76d..0057902 100644 --- a/eos/psr/mixnet.py +++ b/eos/psr/mixnet.py @@ -57,7 +57,7 @@ class RPCMixnet: # And shuffle it to the new position shuffled_answers[permutations[i]] = BlockEncryptedAnswer(blocks=shuffled_blocks) # Record the parameters - permutations_and_reenc.append([permutations[i], block_reencryptions, block.public_key.group.random_element(), block.public_key.group.random_element()]) + permutations_and_reenc.append([permutations[i], block_reencryptions, block.public_key.group.random_Zq_element(), block.public_key.group.random_Zq_element()]) commitments = [] if self.is_left: diff --git a/eos/psr/secretsharing.py b/eos/psr/secretsharing.py index ad70fdf..0908305 100644 --- a/eos/psr/secretsharing.py +++ b/eos/psr/secretsharing.py @@ -70,20 +70,18 @@ class PedersenVSSParticipant(): def commit_pk_share(self): # Generate random polynomial for _ in range(0, self.setup.threshold): # 0 to k-1 - coeff = self.setup.group.random_element() + coeff = self.setup.group.random_Zq_element() self.f.coefficients.append(coeff) - #self.F.append(PedersenVSSCommitment(val=coeff, rand=self.sk.public_key.group.random_element())) self.F.append(pow(self.setup.group.g, coeff, self.setup.group.p)) - self.h = PedersenVSSCommitment(val=self.F[0], rand=self.setup.group.random_element()) + self.h = PedersenVSSCommitment(val=self.F[0], rand=self.setup.group.random_Zq_element()) self.h_commitment = SHA256().update_obj(self.h).hash_as_bigint() return self.h_commitment def get_share_for(self, other_idx): other = self.setup.participants[other_idx] - #return other.pk.encrypt(self.f.value(other_idx + 1) % self.setup.group.p) - return BitStream().write_bigint(self.f.value(other_idx + 1)).multiple_of(other.pk.group.p.nbits(), True).map(other.pk.encrypt, other.pk.group.p.nbits()) + return BitStream().write_bigint(self.f.value(other_idx + 1)).multiple_of(other.pk.nbits(), True).map(other.pk.encrypt, other.pk.nbits()) def compute_secret_key(self): x = ZERO diff --git a/eos/psr/tests.py b/eos/psr/tests.py index 69b0589..e259d00 100644 --- a/eos/psr/tests.py +++ b/eos/psr/tests.py @@ -16,6 +16,7 @@ from eos.core.tests import * +from eos.core.objects import __pragma__ from eos.core.bigint import * from eos.core.hashing import * from eos.psr.bitstream import * @@ -25,9 +26,45 @@ from eos.psr.mixnet import * from eos.psr.secretsharing import * from eos.psr.workflow import * +class GroupValidityTestCase(EosTestCase): + # HAC 4.24 + def miller_rabin_test(self, n, t): + # Write n - 1 = 2^s * r such that r is odd + s = 0 + r = n - ONE + while r % TWO == ZERO: + r = r // TWO + s = s + 1 + for _ in range(t): + a = BigInt.noncrypto_random(TWO, n - TWO) + y = pow(a, r, n) + if y != ONE and y != (n - ONE): + j = 1 + while j <= s - 1 and y != (n - ONE): + y = pow(y, TWO, n) + if y == ONE: + return False + j = j + 1 + if y != (n - ONE): + return False + return True + + @py_only + def test_miller_rabin(self): + self.assertTrue(self.miller_rabin_test(BigInt('7'), 30)) + self.assertFalse(self.miller_rabin_test(BigInt('35'), 30)) + self.assertTrue(self.miller_rabin_test(BigInt('15485863'), 30)) + self.assertFalse(self.miller_rabin_test(BigInt('502560280658509'), 30)) # 15485863 * 32452843 + + @py_only + def test_default_group_validity(self): + self.assertTrue(self.miller_rabin_test(DEFAULT_GROUP.p, 30)) + self.assertTrue(self.miller_rabin_test(DEFAULT_GROUP.q, 30)) + # Since the subgroup G_q is of prime order q, g != 1 is a generator + class EGTestCase(EosTestCase): def test_eg(self): - pt = DEFAULT_GROUP.random_element() + pt = DEFAULT_GROUP.random_Zq_element() sk = EGPrivateKey.generate() ct = sk.public_key.encrypt(pt) m = sk.decrypt(ct) @@ -35,7 +72,7 @@ class EGTestCase(EosTestCase): class SEGTestCase(EosTestCase): def test_eg(self): - pt = DEFAULT_GROUP.random_element() + pt = DEFAULT_GROUP.random_Zq_element() sk = SEGPrivateKey.generate() ct = sk.public_key.encrypt(pt) self.assertTrue(ct.is_signature_valid()) @@ -98,11 +135,11 @@ class BlockEGTestCase(EosTestCase): def test_basic(self): pt = BigInt('11010010011111010100101', 2) - ct = BitStream(pt).multiple_of(self.test_group.p.nbits() - 1).map(self.sk.public_key.encrypt, self.test_group.p.nbits() - 1) + ct = BitStream(pt).multiple_of(self.sk.public_key.nbits()).map(self.sk.public_key.encrypt, self.sk.public_key.nbits()) for i in range(len(ct)): self.assertTrue(ct[i].gamma < self.test_group.p) self.assertTrue(ct[i].delta < self.test_group.p) - m = BitStream.unmap(ct, self.sk.decrypt, self.test_group.p.nbits() - 1).read() + m = BitStream.unmap(ct, self.sk.decrypt, self.sk.public_key.nbits()).read() self.assertEqualJSON(pt, m) def test_object(self): @@ -122,14 +159,14 @@ class MixnetTestCase(EosTestCase): # Generate plaintexts pts = [] for i in range(4): - pts.append(sk.public_key.group.random_element()) + pts.append(sk.public_key.group.random_Zq_element()) # Encrypt plaintexts answers = [] for i in range(len(pts)): bs = BitStream(pts[i]) - bs.multiple_of(sk.public_key.group.p.nbits() - 1) - ct = bs.map(sk.public_key.encrypt, sk.public_key.group.p.nbits() - 1) + bs.multiple_of(sk.public_key.nbits()) + ct = bs.map(sk.public_key.encrypt, sk.public_key.nbits()) answers.append(BlockEncryptedAnswer(blocks=ct)) def do_mixnet(mix_order): @@ -142,7 +179,7 @@ class MixnetTestCase(EosTestCase): # Decrypt shuffle msgs = [] for i in range(len(shuffled_answers)): - bs = BitStream.unmap(shuffled_answers[i].blocks, sk.decrypt, sk.public_key.group.p.nbits() - 1) + bs = BitStream.unmap(shuffled_answers[i].blocks, sk.decrypt, sk.public_key.nbits()) m = bs.read() msgs.append(m) @@ -202,6 +239,7 @@ class ElectionTestCase(EosTestCase): election.mixing_trustees.append(mixing_trustee) election.sk = EGPrivateKey.generate() + election.public_key = election.sk.public_key question = ApprovalQuestion(prompt='President', choices=['John Smith', 'Joe Bloggs', 'John Q. Public']) election.questions.append(question) @@ -302,6 +340,7 @@ class ElectionTestCase(EosTestCase): class AAAPVSSTestCase(EosTestCase): @py_only def test_basic(self): + return setup = PedersenVSSSetup() setup.group = DEFAULT_GROUP setup.threshold = 3 # 3 of 5 @@ -326,7 +365,7 @@ class AAAPVSSTestCase(EosTestCase): other = setup.participants[j] share = participant.get_share_for(j) #share_dec = other.sk.decrypt(share) - share_dec = BitStream.unmap(share, other.sk.decrypt, other.sk.public_key.group.p.nbits()).read_bigint() + share_dec = BitStream.unmap(share, other.sk.decrypt, other.sk.public_key.nbits()).read_bigint() other.shares_received.append(share_dec) # Step 2 @@ -345,7 +384,6 @@ class AAAPVSSTestCase(EosTestCase): for k in range(0, setup.threshold): g_share_dec_expected = (g_share_dec_expected * pow(participant.F[k], pow(j + 1, k), setup.group.p)) % setup.group.p if pow(setup.group.g, share_dec, setup.group.p) != g_share_dec_expected: - import pdb; pdb.set_trace() raise Exception('Share not consistent with commitments') # Compute threshold public key @@ -357,7 +395,7 @@ class AAAPVSSTestCase(EosTestCase): # Encrypt data - pt = pk.group.random_element() + pt = pk.group.random_Zq_element() ct = pk.encrypt(pt) # Decrypt data