From be46f3c6378085e7ba9f229390ce72a2caa957d1 Mon Sep 17 00:00:00 2001 From: Ben Adida <ben@adida.net> Date: Mon, 3 Jan 2011 17:00:44 -0800 Subject: [PATCH] added more datatypes, beginning to build out the data modularity portion of Helios --- helios/datatypes/2011/01.py | 13 +++++ helios/datatypes/__init__.py | 83 +++++++++++++++++++++++++++----- helios/datatypes/core.py | 15 ++++++ helios/datatypes/legacy.py | 32 +++++++++++- helios/datatypes/pkc/__init__.py | 3 ++ helios/datatypes/pkc/elgamal.py | 21 ++++++++ helios/tests.py | 9 ++-- 7 files changed, 160 insertions(+), 16 deletions(-) create mode 100644 helios/datatypes/core.py create mode 100644 helios/datatypes/pkc/__init__.py create mode 100644 helios/datatypes/pkc/elgamal.py diff --git a/helios/datatypes/2011/01.py b/helios/datatypes/2011/01.py index a2edc83..ca56479 100644 --- a/helios/datatypes/2011/01.py +++ b/helios/datatypes/2011/01.py @@ -4,5 +4,18 @@ data types for 2011/01 Helios from helios.datatypes import LDObject +class Trustee(LDObject): + """ + a trustee + """ + + FIELDS = ['uuid', 'public_key', 'public_key_hash', 'pok', 'decryption_factors', 'decryption_proofs', 'email'] + STRUCTURED_FIELDS = { + 'pok' : 'pkc/elgamal/DiscreteLogProof', + 'public_key' : 'pkc/elgamal/PublicKey' + } + + # removed some public key processing for now + class Election(LDObject): pass diff --git a/helios/datatypes/__init__.py b/helios/datatypes/__init__.py index d336b00..ef8ed01 100644 --- a/helios/datatypes/__init__.py +++ b/helios/datatypes/__init__.py @@ -34,7 +34,8 @@ class LDObject(object): data format. For example, a legacy election LDObject instance will wrap an Election object and serialize its fields according to the specs for that version. - To accomodate old JSON types, we allow classes to override default JSON-LD fields. + To accomodate old JSON types, we allow classes to do basically whatever they want, + or to let this base class serialize pure JSON thingies, without the JSON-LD. """ # whether or not to add JSON-LD things @@ -43,26 +44,56 @@ class LDObject(object): # fields to serialize FIELDS = [] + # structured fields + STRUCTURED_FIELDS = {} + + def __init__(self, wrapped_obj): + self.wrapped_obj = wrapped_obj + self.structured_fields = {} + @classmethod - def instantiate(cls, obj): - if not hasattr(obj, 'datatype'): + def get_class(cls, datatype): + # parse datatype string "v31/Election" --> from v31 import Election + parsed_datatype = datatype.split("/") + + # get the module + dynamic_module = __import__(".".join(parsed_datatype[:-1]), globals(), locals(), [], level=-1) + + # go down the attributes to get to the class + dynamic_ptr = dynamic_module + for attr in parsed_datatype[1:]: + dynamic_ptr = getattr(dynamic_ptr, attr) + dynamic_cls = dynamic_ptr + + return dynamic_cls + + @classmethod + def instantiate(cls, obj, datatype=None): + if hasattr(obj, 'datatype') and not datatype: + datatype = getattr(obj, 'datatype') + + if not datatype: raise Exception("no datatype found") - # parse datatype string "v31/Election" --> from v31 import Election - parsed_datatype = obj.datatype.split("/") + # the class + dynamic_cls = cls.get_class(datatype) + + # instantiate it + return_obj = dynamic_cls(obj) - # construct it - dynamic_cls = getattr(__import__(".".join(parsed_datatype[:-1]), globals(), locals(), [], level=-1), parsed_datatype[len(parsed_datatype)-1]) + # go through the subfields and instantiate them too + for subfield_name, subfield_type in dynamic_cls.STRUCTURED_FIELDS.iteritems(): + return_obj.structured_fields[subfield_name] = cls.instantiate(getattr(return_obj.wrapped_obj, subfield_name), datatype = subfield_type) - return dynamic_cls(obj) + return return_obj def set_from_args(self, **kwargs): for f in self.FIELDS: if kwargs.has_key(f): new_val = self.process_value_in(f, kwargs[f]) - setattr(self, f, new_val) + setattr(self.wrapped_obj, f, new_val) else: - setattr(self, f, None) + setattr(self.wrapped_obj, f, None) def serialize(self): return utils.to_json(self.toDict()) @@ -70,17 +101,23 @@ class LDObject(object): def toDict(self, alternate_fields=None): val = {} for f in (alternate_fields or self.FIELDS): - val[f] = self.process_value_out(f, getattr(self, f)) + # is it a structured subfield? + if self.structured_fields.has_key(f): + val[f] = self.structured_fields[f].toDict() + else: + val[f] = self.process_value_out(f, getattr(self.wrapped_obj, f)) return val @classmethod def fromDict(cls, d): + raise Exception("not a good idea yet") + # go through the keys and fix them new_d = {} for k in d.keys(): new_d[str(k)] = d[k] - return cls(**new_d) + return cls(**new_d) @property def hash(self): @@ -125,3 +162,25 @@ class LDObject(object): return other != None and self.uuid == other.uuid + +class ArrayOfObjects(LDObject): + """ + If one type has, as a subtype, an array of things, then this is the structured field used + """ + + def __init__(self, wrapped_array, item_type): + self.item_type = item_type + self.items = [LDObject.instantiate(wrapped_item, item_type) for wrapped_item in wrapped_array] + + def toDict(self): + return [item.serialize() for item in self.items] + +def arrayOf(item_type): + """ + a wrapper for the construtor of the array + returns the constructor + """ + def array_constructor(wrapped_array): + return ArrayOfObjects(wrapped_array, item_type) + + return array_constructor diff --git a/helios/datatypes/core.py b/helios/datatypes/core.py new file mode 100644 index 0000000..c0d488c --- /dev/null +++ b/helios/datatypes/core.py @@ -0,0 +1,15 @@ +""" +core data types +""" + +from helios.datatypes import LDObject + +class BigInteger(LDObject): + """ + A big integer is an integer serialized as a string. + We may want to b64 encode here soon. + """ + + def toDict(self): + return str(self.wrapped_obj) + diff --git a/helios/datatypes/legacy.py b/helios/datatypes/legacy.py index 941f15f..1a10de2 100644 --- a/helios/datatypes/legacy.py +++ b/helios/datatypes/legacy.py @@ -2,4 +2,34 @@ Legacy datatypes for Helios (v3.0) """ -class +from helios.datatypes import LDObject, arrayOf + +class LegacyObject(LDObject): + USE_JSON_LD = False + +class Election(LegacyObject): + FIELDS = ['uuid', 'questions', 'name', 'short_name', 'description', 'voters_hash', 'openreg', + 'frozen_at', 'public_key', 'cast_url', 'use_voter_aliases', 'voting_starts_at', 'voting_ends_at'] + + STRUCTURED_FIELDS = { + 'public_key' : 'legacy/EGPublicKey' + } + +class EncryptedAnswer(LegacyObject): + FIELDS = ['choices', 'individual_proofs', 'overall_proof', 'randomness', 'answer'] + STRUCTURED_FIELDS = { + 'choices': arrayOf('pkc/elgamal/Ciphertext'), + 'individual_proofs': arrayOf('pkc/elgamal/DisjunctiveProof'), + 'overall_proof' : 'pkc/elgamal/DisjunctiveProof', + 'randomness' : 'core/BigInteger' + # answer is not a structured field, it's an as-is integer + } + +class Voter(LegacyObject): + pass + +class CastVote(LegacyObject): + pass + +class Trustee(LegacyObject): + pass diff --git a/helios/datatypes/pkc/__init__.py b/helios/datatypes/pkc/__init__.py new file mode 100644 index 0000000..4783a08 --- /dev/null +++ b/helios/datatypes/pkc/__init__.py @@ -0,0 +1,3 @@ +""" +Public Key Cryptography datatypes +""" diff --git a/helios/datatypes/pkc/elgamal.py b/helios/datatypes/pkc/elgamal.py new file mode 100644 index 0000000..24729e6 --- /dev/null +++ b/helios/datatypes/pkc/elgamal.py @@ -0,0 +1,21 @@ +""" +data types for 2011/01 Helios +""" + +from helios.datatypes import LDObject + +class DiscreteLogProof(LDObject): + FIELDS = ['challenge', 'commitment', 'response'] + STRUCTURED_FIELDS = { + 'challenge' : 'core/BigInteger', + 'commitment' : 'core/BigInteger', + 'response' : 'core/BigInteger'} + +class PublicKey(LDObject): + FIELDS = ['y', 'p', 'g', 'q'] + STRUCTURED_FIELDS = { + 'y' : 'core/BigInteger', + 'p' : 'core/BigInteger', + 'g' : 'core/BigInteger', + 'q' : 'core/BigInteger'} + diff --git a/helios/tests.py b/helios/tests.py index a6e5252..7838baf 100644 --- a/helios/tests.py +++ b/helios/tests.py @@ -98,7 +98,7 @@ class ElectionModelTests(TestCase): self.assertTrue(self.election.has_helios_trustee()) - trustee = self.election.get_helios_trustee() + trustee = self.election.get_helios_trustee() self.assertNotEquals(trustee, None) def test_log(self): @@ -221,10 +221,13 @@ class DatatypeTests(TestCase): def setUp(self): self.election = models.Election.objects.all()[0] + self.election.generate_trustee(ELGAMAL_PARAMS) def test_instantiate(self): - ld_obj = datatypes.LDObject.instantiate(self.election) - + ld_obj = datatypes.LDObject.instantiate(self.election.get_helios_trustee(), '2011/01/Trustee') + foo = ld_obj.serialize() + + ## ## Black box tests -- GitLab