diff --git a/helios/datatypes/2011/01.py b/helios/datatypes/2011/01.py index ca5647947782c23536c45eff5f4d43ba58641d04..547c217c28d05c606594d4e618415a8eac698203 100644 --- a/helios/datatypes/2011/01.py +++ b/helios/datatypes/2011/01.py @@ -2,20 +2,41 @@ data types for 2011/01 Helios """ -from helios.datatypes import LDObject +from helios.datatypes import LDObject, arrayOf 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' - } + 'pok' : 'pkc/elgamal/DiscreteLogProof', + 'public_key' : 'pkc/elgamal/PublicKey' + } # removed some public key processing for now class Election(LDObject): - pass + FIELDS = ['uuid', 'questions', 'name', 'short_name', 'description', 'voters_hash', 'openreg', + 'frozen_at', 'public_key', 'cast_url', 'use_advanced_audit_features', + 'use_voter_aliases', 'voting_starts_at', 'voting_ends_at'] + + STRUCTURED_FIELDS = { + 'public_key' : 'pkc/elgamal/PublicKey', + 'voting_starts_at': 'core/Timestamp', + 'voting_ends_at': 'core/Timestamp', + 'frozen_at': 'core/Timestamp' + } + +class EncryptedAnswer(LDObject): + FIELDS = ['choices', 'individual_proofs', 'overall_proof', 'randomness', 'answer'] + STRUCTURED_FIELDS = { + 'choices': arrayOf('pkc/elgamal/EGCiphertext'), + '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 + } + + diff --git a/helios/datatypes/__init__.py b/helios/datatypes/__init__.py index 7eb3c9c774fdba0e2d24d536b92cdaa2fa0c278c..469bdc7bae85cf5b0f5b244e694c40ff508d67f4 100644 --- a/helios/datatypes/__init__.py +++ b/helios/datatypes/__init__.py @@ -27,6 +27,19 @@ And when data comes in: """ from helios import utils +from helios.crypto import utils as cryptoutils + +## +## utility function +## +def recursiveToDict(obj): + if not obj: + return None + + if type(obj) == list: + return [recursiveToDict(el) for el in obj] + else: + return obj.toDict() class LDObjectContainer(object): """ @@ -46,6 +59,10 @@ class LDObjectContainer(object): def toJSON(self): return self.ld_object.serialize() + @property + def hash(self): + return self.ld_object.hash + class LDObject(object): """ A linked-data object wraps another object and serializes it according to a particular @@ -93,6 +110,14 @@ class LDObject(object): if not datatype: raise Exception("no datatype found") + # nulls + if not obj: + return None + + # array + if isArray(datatype): + return [cls.instantiate(el, datatype = datatype.element_type) for el in obj] + # the class dynamic_cls = cls.get_class(datatype) @@ -121,7 +146,7 @@ class LDObject(object): for f in (alternate_fields or self.FIELDS): # is it a structured subfield? if self.structured_fields.has_key(f): - val[f] = self.structured_fields[f].toDict() + val[f] = recursiveToDict(self.structured_fields[f]) else: val[f] = self.process_value_out(f, getattr(self.wrapped_obj, f)) return val @@ -140,7 +165,7 @@ class LDObject(object): @property def hash(self): s = self.serialize() - return utils.hash_b64(s) + return cryptoutils.hash_b64(s) def process_value_in(self, field_name, field_value): """ @@ -193,12 +218,13 @@ class ArrayOfObjects(LDObject): def toDict(self): return [item.serialize() for item in self.items] -def arrayOf(item_type): +class arrayOf(object): """ a wrapper for the construtor of the array returns the constructor """ - def array_constructor(wrapped_array): - return ArrayOfObjects(wrapped_array, item_type) + def __init__(self, element_type): + self.element_type = element_type - return array_constructor +def isArray(field_type): + return type(field_type) == arrayOf diff --git a/helios/datatypes/legacy.py b/helios/datatypes/legacy.py index a7df75e92ffb1ce9fa603193d2b383522856a98f..900246674336433f5039caeaeddfd620a28cb349 100644 --- a/helios/datatypes/legacy.py +++ b/helios/datatypes/legacy.py @@ -4,6 +4,19 @@ Legacy datatypes for Helios (v3.0) from helios.datatypes import LDObject, arrayOf +## +## utilities + +class DictObject(object): + def __init__(self, d): + self.d = d + + def __getattr__(self, k): + return self.d[k] + +## +## + class LegacyObject(LDObject): USE_JSON_LD = False @@ -19,17 +32,33 @@ class Election(LegacyObject): } class EncryptedAnswer(LegacyObject): + FIELDS = ['choices', 'individual_proofs', 'overall_proof'] + STRUCTURED_FIELDS = { + 'choices': arrayOf('legacy/EGCiphertext'), + 'individual_proofs': arrayOf('legacy/EGZKDisjunctiveProof'), + 'overall_proof' : 'legacy/EGZKDisjunctiveProof' + } + +class EncryptedAnswerWithDecryption(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', + 'choices': arrayOf('legacy/EGCiphertext'), + 'individual_proofs': arrayOf('legacy/EGZKDisjunctiveProof'), + 'overall_proof' : 'legacy/EGZKDisjunctiveProof', 'randomness' : 'core/BigInteger' - # answer is not a structured field, it's an as-is integer + } + +class EncryptedVote(LegacyObject): + """ + An encrypted ballot + """ + FIELDS = ['answers', 'election_hash', 'election_uuid'] + STRUCTURED_FIELDS = { + 'answers' : arrayOf('legacy/EncryptedAnswer') } class Voter(LegacyObject): - FIELDS = ['election_uuid', 'uuid', 'voter_type', 'voter_id_hash', 'name', 'alias'] + FIELDS = ['election_uuid', 'uuid', 'voter_type', 'voter_id_hash', 'name'] ALIASED_VOTER_FIELDS = ['election_uuid', 'uuid', 'alias'] @@ -43,8 +72,15 @@ class Voter(LegacyObject): return super(Voter,self).toDict() +class ShortCastVote(LegacyObject): + FIELDS = ['cast_at', 'voter_uuid', 'voter_hash', 'vote_hash'] + STRUCTURED_FIELDS = {'cast_at' : 'core/Timestamp'} + class CastVote(LegacyObject): FIELDS = ['vote', 'cast_at', 'voter_uuid', 'voter_hash', 'vote_hash'] + STRUCTURED_FIELDS = { + 'cast_at' : 'core/Timestamp', + 'vote' : 'legacy/EncryptedVote'} class Trustee(LegacyObject): FIELDS = ['uuid', 'public_key', 'public_key_hash', 'pok', 'decryption_factors', 'decryption_proofs', 'email'] @@ -52,8 +88,8 @@ class Trustee(LegacyObject): STRUCTURED_FIELDS = { 'public_key' : 'legacy/EGPublicKey', 'pok': 'legacy/DLogProof', - 'decryption_factors': arrayOf('core/BigInteger'), - 'decryption_proofs' : arrayOf('legacy/DLogProof')} + 'decryption_factors': arrayOf(arrayOf('core/BigInteger')), + 'decryption_proofs' : arrayOf(arrayOf('legacy/DLogProof'))} class EGPublicKey(LegacyObject): FIELDS = ['y', 'p', 'g', 'q'] @@ -62,3 +98,47 @@ class EGPublicKey(LegacyObject): 'p': 'core/BigInteger', 'q': 'core/BigInteger', 'g': 'core/BigInteger'} + +class EGCiphertext(LegacyObject): + FIELDS = ['alpha','beta'] + STRUCTURED_FIELDS = { + 'alpha': 'core/BigInteger', + 'beta' : 'core/BigInteger'} + +class EGZKProofCommitment(LegacyObject): + FIELDS = ['A', 'B'] + STRUCTURED_FIELDS = { + 'A' : 'core/BigInteger', + 'B' : 'core/BigInteger'} + + def __init__(self, wrapped_obj): + super(EGZKProofCommitment, self).__init__(DictObject(wrapped_obj)) + +class EGZKProof(LegacyObject): + FIELDS = ['commitment', 'challenge', 'response'] + STRUCTURED_FIELDS = { + 'commitment': 'legacy/EGZKProofCommitment', + 'challenge' : 'core/BigInteger', + 'response' : 'core/BigInteger'} + +class EGZKDisjunctiveProof(LegacyObject): + FIELDS = ['proofs'] + STRUCTURED_FIELDS = { + 'proofs': arrayOf('legacy/EGZKProof')} + + def toDict(self): + "hijack toDict and make it return the proofs array only, since that's the spec for legacy" + return super(EGZKDisjunctiveProof, self).toDict()['proofs'] + +class DLogProof(LegacyObject): + FIELDS = ['commitment', 'challenge', 'response'] + STRUCTURED_FIELDS = { + 'commitment' : 'core/BigInteger', + 'challenge' : 'core/BigInteger', + 'response' : 'core/BigInteger'} + + def __init__(self, wrapped_obj): + if type(wrapped_obj) == dict: + super(DLogProof, self).__init__(DictObject(wrapped_obj)) + else: + super(DLogProof, self).__init__(wrapped_obj) diff --git a/helios/migrations/0003_v3_1_election_specific_voters_with_passwords.py b/helios/migrations/0003_v3_1_election_specific_voters_with_passwords.py index 762f72b549ee4dd6c8335bd85a1c1c323820a073..30f0fe04bf1166ffe65d1c4db31ab8ab457a2c55 100644 --- a/helios/migrations/0003_v3_1_election_specific_voters_with_passwords.py +++ b/helios/migrations/0003_v3_1_election_specific_voters_with_passwords.py @@ -10,7 +10,12 @@ class Migration(DataMigration): """ update the voters data objects to point to users when it makes sense, and otherwise to copy the data needed from the users table. + make all elections legacy, because before now they are. """ + for e in orm.Election.objects.all(): + e.datatype = 'legacy/Election' + e.save() + for v in orm.Voter.objects.all(): user = orm['auth.User'].objects.get(user_type = v.voter_type, user_id = v.voter_id) @@ -27,7 +32,18 @@ class Migration(DataMigration): # also, update tinyhash for all votes for cv in orm.CastVote.objects.all(): - cv.set_tinyhash() + safe_hash = cv.vote_hash + for c in ['/', '+']: + safe_hash = safe_hash.replace(c,'') + + length = 8 + while True: + vote_tinyhash = safe_hash[:length] + if orm.CastVote.objects.filter(vote_tinyhash = vote_tinyhash).count() == 0: + break + length += 1 + + cv.vote_tinyhash = vote_tinyhash cv.save() diff --git a/helios/models.py b/helios/models.py index 0e5f679c7998cf953f0749203cf18ad3bd31b270..89e3d4b7b8a43a7206bb1af040e580aef2005cac 100644 --- a/helios/models.py +++ b/helios/models.py @@ -675,6 +675,10 @@ class Voter(HeliosModel): def get_by_user(cls, user): return cls.objects.select_related().filter(user = user).order_by('-cast_at') + @property + def datatype(self): + return self.election.datatype.replace('Election', 'Voter') + @property def vote_tinyhash(self): """ @@ -697,6 +701,15 @@ class Voter(HeliosModel): def voter_id(self): return self.user.user_id + @property + def voter_id_hash(self): + if self.voter_login_id: + # for backwards compatibility with v3.0, and since it doesn't matter + # too much if we hash the email or the unique login ID here. + return utils.hash_b64(self.voter_login_id) + else: + return utils.hash_b64(self.voter_id) + @property def voter_type(self): return self.user.user_type @@ -751,6 +764,10 @@ class CastVote(HeliosModel): verified_at = models.DateTimeField(null=True) invalidated_at = models.DateTimeField(null=True) + @property + def datatype(self): + return self.voter.datatype.replace('Voter', 'CastVote') + @property def voter_uuid(self): return self.voter.uuid @@ -890,6 +907,10 @@ class Trustee(HeliosModel): @classmethod def get_by_election_and_email(cls, election, email): return cls.objects.get(election = election, email = email) + + @property + def datatype(self): + return self.election.datatype.replace('Election', 'Trustee') def verify_decryption_proofs(self): """ diff --git a/helios/views.py b/helios/views.py index fd250bde6160d86b650b0eee058d13aee2d63c72..b9a8d8bd29b8480c00549224f429c201178c3b81 100644 --- a/helios/views.py +++ b/helios/views.py @@ -1172,7 +1172,7 @@ def ballot_list(request, election): after = datetime.datetime.strptime(request.GET['after'], '%Y-%m-%d %H:%M:%S') voters = Voter.get_by_election(election, cast=True, order_by='cast_at', limit=limit, after=after) - return [v.last_cast_vote().toJSONDict(include_vote=False) for v in voters] + return [v.last_cast_vote().toJSONDict() for v in voters]