Correctly map messages to G_q instead of simply using Z_p*
This commit is contained in:
parent
1642b0eba9
commit
db74f67474
@ -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
|
||||
|
@ -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'),
|
||||
@ -88,6 +91,16 @@ class BigInt(EosObject):
|
||||
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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -26,12 +26,21 @@ 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(
|
||||
p=BigInt('FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF', 16),
|
||||
@ -42,10 +51,12 @@ class EGPublicKey(EmbeddedObject):
|
||||
group = EmbeddedObjectField(CyclicGroup)
|
||||
X = EmbeddedObjectField(BigInt)
|
||||
|
||||
# HAC 8.18
|
||||
def encrypt(self, message):
|
||||
message += ONE # Dodgy hack to allow zeroes
|
||||
def nbits(self):
|
||||
# Our messages are restricted to G_q
|
||||
return self.group.q.nbits() - 1
|
||||
|
||||
# HAC 8.18
|
||||
def _encrypt(self, message):
|
||||
if message <= ZERO:
|
||||
raise Exception('Invalid message')
|
||||
if message >= self.group.p:
|
||||
@ -59,6 +70,23 @@ class EGPublicKey(EmbeddedObject):
|
||||
|
||||
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
|
||||
|
||||
# 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 (
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user