diff --git a/auth/jsonfield.py b/auth/jsonfield.py index 6a026cf272d16f2f657d87b41be4c0b54b14c97a..ff63893e3a44cf841d1dd32350c36970cdc5c6f6 100644 --- a/auth/jsonfield.py +++ b/auth/jsonfield.py @@ -12,14 +12,19 @@ from django.utils import simplejson as json from django.core.serializers.json import DjangoJSONEncoder class JSONField(models.TextField): - """JSONField is a generic textfield that neatly serializes/unserializes - JSON objects seamlessly""" + """ + JSONField is a generic textfield that neatly serializes/unserializes + JSON objects seamlessly. + + deserialization_params added on 2011-01-09 to provide additional hints at deserialization time + """ # Used so to_python() is called __metaclass__ = models.SubfieldBase - def __init__(self, json_type=None, **kwargs): + def __init__(self, json_type=None, deserialization_params=None, **kwargs): self.json_type = json_type + self.deserialization_params = deserialization_params super(JSONField, self).__init__(**kwargs) def to_python(self, value): @@ -38,7 +43,7 @@ class JSONField(models.TextField): parsed_value = json.loads(value) if self.json_type and parsed_value: - parsed_value = self.json_type.fromJSONDict(parsed_value) + parsed_value = self.json_type.fromJSONDict(parsed_value, **self.deserialization_params) return parsed_value diff --git a/helios/crypto/elgamal.py b/helios/crypto/elgamal.py new file mode 100644 index 0000000000000000000000000000000000000000..8d62d6a6620e7d39f072f6e461b5e3a9c02bb107 --- /dev/null +++ b/helios/crypto/elgamal.py @@ -0,0 +1,530 @@ +""" +ElGamal Algorithms for the Helios Voting System + +This is a copy of algs.py now made more El-Gamal specific in naming, +for modularity purposes. + +Ben Adida +ben@adida.net +""" + +import math, hashlib, logging +import randpool, number + +import numtheory + +from algs import Utils + +class Cryptosystem(object): + def __init__(self): + self.p = None + self.q = None + self.g = None + + @classmethod + def generate(cls, n_bits): + """ + generate an El-Gamal environment. Returns an instance + of ElGamal(), with prime p, group size q, and generator g + """ + + EG = cls() + + # find a prime p such that (p-1)/2 is prime q + EG.p = Utils.random_safe_prime(n_bits) + + # q is the order of the group + # FIXME: not always p-1/2 + EG.q = (EG.p-1)/2 + + # find g that generates the q-order subgroup + while True: + EG.g = Utils.random_mpz_lt(EG.p) + if pow(EG.g, EG.q, EG.p) == 1: + break + + return EG + + def generate_keypair(self): + """ + generates a keypair in the setting + """ + + keypair = KeyPair() + keypair.generate(self.p, self.q, self.g) + + return keypair + +class KeyPair(object): + def __init__(self): + self.pk = PublicKey() + self.sk = SecretKey() + + def generate(self, p, q, g): + """ + Generate an ElGamal keypair + """ + self.pk.g = g + self.pk.p = p + self.pk.q = q + + self.sk.x = Utils.random_mpz_lt(p) + self.pk.y = pow(g, self.sk.x, p) + + self.sk.pk = self.pk + +class PublicKey: + def __init__(self): + self.y = None + self.p = None + self.g = None + self.q = None + + def encrypt_with_r(self, plaintext, r, encode_message= False): + """ + expecting plaintext.m to be a big integer + """ + ciphertext = Ciphertext() + ciphertext.pk = self + + # make sure m is in the right subgroup + if encode_message: + y = plaintext.m + 1 + if pow(y, self.q, self.p) == 1: + m = y + else: + m = -y % self.p + else: + m = plaintext.m + + ciphertext.alpha = pow(self.g, r, self.p) + ciphertext.beta = (m * pow(self.y, r, self.p)) % self.p + + return ciphertext + + def encrypt_return_r(self, plaintext): + """ + Encrypt a plaintext and return the randomness just generated and used. + """ + r = Utils.random_mpz_lt(self.q) + ciphertext = self.encrypt_with_r(plaintext, r) + + return [ciphertext, r] + + def encrypt(self, plaintext): + """ + Encrypt a plaintext, obscure the randomness. + """ + return self.encrypt_return_r(plaintext)[0] + + def __mul__(self,other): + if other == 0 or other == 1: + return self + + # check p and q + if self.p != other.p or self.q != other.q or self.g != other.g: + raise Exception("incompatible public keys") + + result = PublicKey() + result.p = self.p + result.q = self.q + result.g = self.g + result.y = (self.y * other.y) % result.p + return result + + def verify_sk_proof(self, dlog_proof, challenge_generator = None): + """ + verify the proof of knowledge of the secret key + g^response = commitment * y^challenge + """ + left_side = pow(self.g, dlog_proof.response, self.p) + right_side = (dlog_proof.commitment * pow(self.y, dlog_proof.challenge, self.p)) % self.p + + expected_challenge = challenge_generator(dlog_proof.commitment) % self.q + + return ((left_side == right_side) and (dlog_proof.challenge == expected_challenge)) + + +class SecretKey: + def __init__(self): + self.x = None + self.pk = None + + def decryption_factor(self, ciphertext): + """ + provide the decryption factor, not yet inverted because of needed proof + """ + return pow(ciphertext.alpha, self.x, self.pk.p) + + def decryption_factor_and_proof(self, ciphertext, challenge_generator=None): + """ + challenge generator is almost certainly + EG_fiatshamir_challenge_generator + """ + if not challenge_generator: + challenge_generator = fiatshamir_challenge_generator + + dec_factor = self.decryption_factor(ciphertext) + + proof = ZKProof.generate(self.pk.g, ciphertext.alpha, self.x, self.pk.p, self.pk.q, challenge_generator) + + return dec_factor, proof + + def decrypt(self, ciphertext, dec_factor = None, decode_m=False): + """ + Decrypt a ciphertext. Optional parameter decides whether to encode the message into the proper subgroup. + """ + if not dec_factor: + dec_factor = self.decryption_factor(ciphertext) + + m = (Utils.inverse(dec_factor, self.pk.p) * ciphertext.beta) % self.pk.p + + if decode_m: + # get m back from the q-order subgroup + if m < self.pk.q: + y = m + else: + y = -m % self.pk.p + + return Plaintext(y-1, self.pk) + else: + return Plaintext(m, self.pk) + + def prove_decryption(self, ciphertext): + """ + given g, y, alpha, beta/(encoded m), prove equality of discrete log + with Chaum Pedersen, and that discrete log is x, the secret key. + + Prover sends a=g^w, b=alpha^w for random w + Challenge c = sha1(a,b) with and b in decimal form + Prover sends t = w + xc + + Verifier will check that g^t = a * y^c + and alpha^t = b * beta/m ^ c + """ + + m = (Utils.inverse(pow(ciphertext.alpha, self.x, self.pk.p), self.pk.p) * ciphertext.beta) % self.pk.p + beta_over_m = (ciphertext.beta * Utils.inverse(m, self.pk.p)) % self.pk.p + + # pick a random w + w = Utils.random_mpz_lt(self.pk.q) + a = pow(self.pk.g, w, self.pk.p) + b = pow(ciphertext.alpha, w, self.pk.p) + + c = int(hashlib.sha1(str(a) + "," + str(b)).hexdigest(),16) + + t = (w + self.x * c) % self.pk.q + + return m, { + 'commitment' : {'A' : str(a), 'B': str(b)}, + 'challenge' : str(c), + 'response' : str(t) + } + + def prove_sk(self, challenge_generator): + """ + Generate a PoK of the secret key + Prover generates w, a random integer modulo q, and computes commitment = g^w mod p. + Verifier provides challenge modulo q. + Prover computes response = w + x*challenge mod q, where x is the secret key. + """ + w = Utils.random_mpz_lt(self.pk.q) + commitment = pow(self.pk.g, w, self.pk.p) + challenge = challenge_generator(commitment) % self.pk.q + response = (w + (self.x * challenge)) % self.pk.q + + return DLogProof(commitment, challenge, response) + + +class Plaintext: + def __init__(self, m = None, pk = None): + self.m = m + self.pk = pk + +class Ciphertext: + def __init__(self, alpha=None, beta=None, pk=None): + self.pk = pk + self.alpha = alpha + self.beta = beta + + def __mul__(self,other): + """ + Homomorphic Multiplication of ciphertexts. + """ + if type(other) == int and (other == 0 or other == 1): + return self + + if self.pk != other.pk: + logging.info(self.pk) + logging.info(other.pk) + raise Exception('different PKs!') + + new = Ciphertext() + + new.pk = self.pk + new.alpha = (self.alpha * other.alpha) % self.pk.p + new.beta = (self.beta * other.beta) % self.pk.p + + return new + + def reenc_with_r(self, r): + """ + We would do this homomorphically, except + that's no good when we do plaintext encoding of 1. + """ + new_c = Ciphertext() + new_c.alpha = (self.alpha * pow(self.pk.g, r, self.pk.p)) % self.pk.p + new_c.beta = (self.beta * pow(self.pk.y, r, self.pk.p)) % self.pk.p + new_c.pk = self.pk + + return new_c + + def reenc_return_r(self): + """ + Reencryption with fresh randomness, which is returned. + """ + r = Utils.random_mpz_lt(self.pk.q) + new_c = self.reenc_with_r(r) + return [new_c, r] + + def reenc(self): + """ + Reencryption with fresh randomness, which is kept obscured (unlikely to be useful.) + """ + return self.reenc_return_r()[0] + + def __eq__(self, other): + """ + Check for ciphertext equality. + """ + if other == None: + return False + + return (self.alpha == other.alpha and self.beta == other.beta) + + def generate_encryption_proof(self, plaintext, randomness, challenge_generator): + """ + Generate the disjunctive encryption proof of encryption + """ + # random W + w = Utils.random_mpz_lt(self.pk.q) + + # build the proof + proof = ZKProof() + + # compute A=g^w, B=y^w + proof.commitment['A'] = pow(self.pk.g, w, self.pk.p) + proof.commitment['B'] = pow(self.pk.y, w, self.pk.p) + + # generate challenge + proof.challenge = challenge_generator(proof.commitment); + + # Compute response = w + randomness * challenge + proof.response = (w + (randomness * proof.challenge)) % self.pk.q; + + return proof; + + def simulate_encryption_proof(self, plaintext, challenge=None): + # generate a random challenge if not provided + if not challenge: + challenge = Utils.random_mpz_lt(self.pk.q) + + proof = ZKProof() + proof.challenge = challenge + + # compute beta/plaintext, the completion of the DH tuple + beta_over_plaintext = (self.beta * Utils.inverse(plaintext.m, self.pk.p)) % self.pk.p + + # random response, does not even need to depend on the challenge + proof.response = Utils.random_mpz_lt(self.pk.q); + + # now we compute A and B + proof.commitment['A'] = (Utils.inverse(pow(self.alpha, proof.challenge, self.pk.p), self.pk.p) * pow(self.pk.g, proof.response, self.pk.p)) % self.pk.p + proof.commitment['B'] = (Utils.inverse(pow(beta_over_plaintext, proof.challenge, self.pk.p), self.pk.p) * pow(self.pk.y, proof.response, self.pk.p)) % self.pk.p + + return proof + + def generate_disjunctive_encryption_proof(self, plaintexts, real_index, randomness, challenge_generator): + # note how the interface is as such so that the result does not reveal which is the real proof. + + proofs = [None for p in plaintexts] + + # go through all plaintexts and simulate the ones that must be simulated. + for p_num in range(len(plaintexts)): + if p_num != real_index: + proofs[p_num] = self.simulate_encryption_proof(plaintexts[p_num]) + + # the function that generates the challenge + def real_challenge_generator(commitment): + # set up the partial real proof so we're ready to get the hash + proofs[real_index] = ZKProof() + proofs[real_index].commitment = commitment + + # get the commitments in a list and generate the whole disjunctive challenge + commitments = [p.commitment for p in proofs] + disjunctive_challenge = challenge_generator(commitments); + + # now we must subtract all of the other challenges from this challenge. + real_challenge = disjunctive_challenge + for p_num in range(len(proofs)): + if p_num != real_index: + real_challenge = real_challenge - proofs[p_num].challenge + + # make sure we mod q, the exponent modulus + return real_challenge % self.pk.q + + # do the real proof + real_proof = self.generate_encryption_proof(plaintexts[real_index], randomness, real_challenge_generator) + + # set the real proof + proofs[real_index] = real_proof + + return ZKDisjunctiveProof(proofs) + + def verify_encryption_proof(self, plaintext, proof): + """ + Checks for the DDH tuple g, y, alpha, beta/plaintext. + (PoK of randomness r.) + + Proof contains commitment = {A, B}, challenge, response + """ + + # check that g^response = A * alpha^challenge + first_check = (pow(self.pk.g, proof.response, self.pk.p) == ((pow(self.alpha, proof.challenge, self.pk.p) * proof.commitment['A']) % self.pk.p)) + + # check that y^response = B * (beta/m)^challenge + beta_over_m = (self.beta * Utils.inverse(plaintext.m, self.pk.p)) % self.pk.p + second_check = (pow(self.pk.y, proof.response, self.pk.p) == ((pow(beta_over_m, proof.challenge, self.pk.p) * proof.commitment['B']) % self.pk.p)) + + # print "1,2: %s %s " % (first_check, second_check) + return (first_check and second_check) + + def verify_disjunctive_encryption_proof(self, plaintexts, proof, challenge_generator): + """ + plaintexts and proofs are all lists of equal length, with matching. + + overall_challenge is what all of the challenges combined should yield. + """ + for i in range(len(plaintexts)): + # if a proof fails, stop right there + if not self.verify_encryption_proof(plaintexts[i], proof.proofs[i]): + print "bad proof %s, %s, %s" % (i, plaintexts[i], proof.proofs[i]) + return False + + # logging.info("made it past the two encryption proofs") + + # check the overall challenge + return (challenge_generator([p.commitment for p in proof.proofs]) == (sum([p.challenge for p in proof.proofs]) % self.pk.q)) + + def verify_decryption_proof(self, plaintext, proof): + """ + Checks for the DDH tuple g, alpha, y, beta/plaintext + (PoK of secret key x.) + """ + return False + + def verify_decryption_factor(self, dec_factor, dec_proof, public_key): + """ + when a ciphertext is decrypted by a dec factor, the proof needs to be checked + """ + pass + + def decrypt(self, decryption_factors, public_key): + """ + decrypt a ciphertext given a list of decryption factors (from multiple trustees) + For now, no support for threshold + """ + running_decryption = self.beta + for dec_factor in decryption_factors: + running_decryption = (running_decryption * Utils.inverse(dec_factor, public_key.p)) % public_key.p + + return running_decryption + + def to_string(self): + return "%s,%s" % (self.alpha, self.beta) + + @classmethod + def from_string(cls, str): + """ + expects alpha,beta + """ + split = str.split(",") + return cls.from_dict({'alpha' : split[0], 'beta' : split[1]}) + +class ZKProof(object): + def __init__(self): + self.commitment = {'A':None, 'B':None} + self.challenge = None + self.response = None + + @classmethod + def generate(cls, little_g, little_h, x, p, q, challenge_generator): + """ + generate a DDH tuple proof, where challenge generator is + almost certainly EG_fiatshamir_challenge_generator + """ + + # generate random w + w = Utils.random_mpz_lt(q) + + # create proof instance + proof = cls() + + # compute A = little_g^w, B=little_h^w + proof.commitment['A'] = pow(little_g, w, p) + proof.commitment['B'] = pow(little_h, w, p) + + # get challenge + proof.challenge = challenge_generator(proof.commitment) + + # compute response + proof.response = (w + (x * proof.challenge)) % q + + # return proof + return proof + + def verify(self, little_g, little_h, big_g, big_h, p, q, challenge_generator=None): + """ + Verify a DH tuple proof + """ + # check that little_g^response = A * big_g^challenge + first_check = (pow(little_g, self.response, p) == ((pow(big_g, self.challenge, p) * self.commitment['A']) % p)) + + # check that little_h^response = B * big_h^challenge + second_check = (pow(little_h, self.response, p) == ((pow(big_h, self.challenge, p) * self.commitment['B']) % p)) + + # check the challenge? + third_check = True + + if challenge_generator: + third_check = (self.challenge == challenge_generator(self.commitment)) + + return (first_check and second_check and third_check) + +class ZKDisjunctiveProof: + def __init__(self, proofs = None): + self.proofs = proofs + +class DLogProof(object): + def __init__(self, commitment, challenge, response): + self.commitment = commitment + self.challenge = challenge + self.response = response + +def disjunctive_challenge_generator(commitments): + array_to_hash = [] + for commitment in commitments: + array_to_hash.append(str(commitment['A'])) + array_to_hash.append(str(commitment['B'])) + + string_to_hash = ",".join(array_to_hash) + return int(hashlib.sha1(string_to_hash).hexdigest(),16) + +# a challenge generator for Fiat-Shamir with A,B commitment +def fiatshamir_challenge_generator(commitment): + return disjunctive_challenge_generator([commitment]) + +def DLog_challenge_generator(commitment): + string_to_hash = str(commitment) + return int(hashlib.sha1(string_to_hash).hexdigest(),16) + diff --git a/helios/datatypes/__init__.py b/helios/datatypes/__init__.py index 469bdc7bae85cf5b0f5b244e694c40ff508d67f4..4735ba97a3b2cc55f191fc0597135e4e87af6bfe 100644 --- a/helios/datatypes/__init__.py +++ b/helios/datatypes/__init__.py @@ -79,9 +79,12 @@ class LDObject(object): # fields to serialize FIELDS = [] - # structured fields + # structured fields are other LD objects, not simple types STRUCTURED_FIELDS = {} + # the underlying object type, which contains algorithms, to instantiate by default + WRAPPED_OBJ_CLASS = None + def __init__(self, wrapped_obj): self.wrapped_obj = wrapped_obj self.structured_fields = {} @@ -151,16 +154,48 @@ class LDObject(object): val[f] = self.process_value_out(f, getattr(self.wrapped_obj, f)) return val + def loadDataFromDict(self, d): + """ + load data from a dictionary + """ + + # the structured fields + structured_fields = self.STRUCTURED_FIELDS.keys() + + # go through the fields and set them properly + # on the newly instantiated object + for f in self.FIELDS: + if f in structured_fields: + # a structured ld field, recur + sub_ld_object = self.fromDict(d[f], type_hint = self.STRUCTURED_FIELDS[f]) + self.structured_fields[f] = sub_ld_object + + # set the field on the wrapped object too + setattr(self.wrapped_obj, f, sub_ld_object.wrapped_obj) + else: + # a simple type + new_val = self.process_value_in(f, d[f]) + setattr(self.wrapped_obj, f, new_val) + @classmethod - def fromDict(cls, d): - raise Exception("not a good idea yet") + def fromDict(cls, d, type_hint=None): + # the LD type is either in d or in type_hint + # FIXME: get this from the dictionary itself + ld_type = type_hint - # go through the keys and fix them - new_d = {} - for k in d.keys(): - new_d[str(k)] = d[k] - - return cls(**new_d) + # get the LD class so we know what wrapped object to instantiate + ld_cls = cls.get_class(ld_type) + + wrapped_obj_cls = ld_cls.WRAPPED_OBJ_CLASS + wrapped_obj = wrapped_obj_cls() + + # then instantiate the LD object and load the data + ld_obj = ld_cls(wrapped_obj) + ld_obj.loadDataFromDict(d) + + return ld_obj + + fromJSONDict = fromDict @property def hash(self): @@ -181,7 +216,7 @@ class LDObject(object): return field_value def _process_value_in(self, field_name, field_value): - return None + return field_value def process_value_out(self, field_name, field_value): """ diff --git a/helios/datatypes/core.py b/helios/datatypes/core.py index 93384905340300b8b61eb5061b874a520ec10a72..aacb833b3dce083b7ec1efdcba69fde1331c4349 100644 --- a/helios/datatypes/core.py +++ b/helios/datatypes/core.py @@ -9,6 +9,7 @@ class BigInteger(LDObject): A big integer is an integer serialized as a string. We may want to b64 encode here soon. """ + WRAPPED_OBJ_CLASS = int def toDict(self): if self.wrapped_obj: @@ -16,6 +17,10 @@ class BigInteger(LDObject): else: return None + def loadDataFromDict(self, d): + "take a string and cast it to an int -- which is a big int too" + self.wrapped_obj = int(d) + class Timestamp(LDObject): def toDict(self): if self.wrapped_obj: diff --git a/helios/datatypes/legacy.py b/helios/datatypes/legacy.py index 900246674336433f5039caeaeddfd620a28cb349..3f6902ab983bd8fcd376ca69498a4603752bcfd0 100644 --- a/helios/datatypes/legacy.py +++ b/helios/datatypes/legacy.py @@ -18,6 +18,7 @@ class DictObject(object): ## class LegacyObject(LDObject): + WRAPPED_OBJ_CLASS = dict USE_JSON_LD = False class Election(LegacyObject): @@ -142,3 +143,15 @@ class DLogProof(LegacyObject): super(DLogProof, self).__init__(DictObject(wrapped_obj)) else: super(DLogProof, self).__init__(wrapped_obj) + +class Result(LegacyObject): + pass + +class Questions(LegacyObject): + pass + +class Tally(LegacyObject): + pass + +class Eligibility(LegacyObject): + pass diff --git a/helios/datatypes/pkc/elgamal.py b/helios/datatypes/pkc/elgamal.py index 24729e6399904a9536b96bc61a290e43e9ef2a3a..21f7cc922f5ce62388b4c4781eb4a14f251f8233 100644 --- a/helios/datatypes/pkc/elgamal.py +++ b/helios/datatypes/pkc/elgamal.py @@ -3,6 +3,7 @@ data types for 2011/01 Helios """ from helios.datatypes import LDObject +from helios.crypto import elgamal class DiscreteLogProof(LDObject): FIELDS = ['challenge', 'commitment', 'response'] @@ -12,6 +13,8 @@ class DiscreteLogProof(LDObject): 'response' : 'core/BigInteger'} class PublicKey(LDObject): + WRAPPED_OBJ_CLASS = elgamal.PublicKey + FIELDS = ['y', 'p', 'g', 'q'] STRUCTURED_FIELDS = { 'y' : 'core/BigInteger', diff --git a/helios/models.py b/helios/models.py index 89e3d4b7b8a43a7206bb1af040e580aef2005cac..3ea75ed9f0cb1062bec0b410256e4ac461a70c26 100644 --- a/helios/models.py +++ b/helios/models.py @@ -57,13 +57,22 @@ class Election(HeliosModel): private_p = models.BooleanField(default=False, null=False) description = models.TextField() - public_key = JSONField(algs.EGPublicKey, null=True) - private_key = JSONField(algs.EGSecretKey, null=True) - questions = JSONField(null=True) + public_key = JSONField(datatypes.LDObject, + deserialization_params = {'type_hint' : 'legacy/EGPublicKey'}, + null=True) + private_key = JSONField(datatypes.LDObject, + deserialization_params = {'type_hint' : 'legacy/EGSecretKey'}, + null=True) + + questions = JSONField(datatypes.LDObject, + deserialization_params = {'type_hint' : 'legacy/Questions'}, + null=True) # eligibility is a JSON field, which lists auth_systems and eligibility details for that auth_system, e.g. # [{'auth_system': 'cas', 'constraint': [{'year': 'u12'}, {'year':'u13'}]}, {'auth_system' : 'password'}, {'auth_system' : 'openid', 'constraint': [{'host':'http://myopenid.com'}]}] - eligibility = JSONField(null=True) + eligibility = JSONField(datatypes.LDObject, + deserialization_params = {'type_hint' : 'legacy/Eligibility'}, + null=True) # open registration? # this is now used to indicate the state of registration, @@ -115,12 +124,17 @@ class Election(HeliosModel): # encrypted tally, each a JSON string # used only for homomorphic tallies - encrypted_tally = JSONField(electionalgs.Tally, null=True) + encrypted_tally = JSONField(datatypes.LDObject, + deserialization_params={'type_hint': 'legacy/Tally'}, + null=True) # results of the election - result = JSONField(null=True) + result = JSONField(datatypes.LDObject, + deserialization_params = {'type_hint' : 'legacy/Result'}, + null=True) # decryption proof, a JSON object + # no longer needed since it's all trustees result_proof = JSONField(null=True) @property @@ -585,7 +599,9 @@ class Voter(HeliosModel): alias = models.CharField(max_length = 100, null=True) # we keep a copy here for easy tallying - vote = JSONField(electionalgs.EncryptedVote, null=True) + vote = JSONField(datatypes.LDObject, + deserialization_params = {'type_hint': 'legacy/EncryptedVote'}, + null=True) vote_hash = models.CharField(max_length = 100, null=True) cast_at = models.DateTimeField(auto_now_add=False, null=True) @@ -745,8 +761,9 @@ class CastVote(HeliosModel): # the reference to the voter provides the voter_uuid voter = models.ForeignKey(Voter) - # a json array, which should contain election_uuid and election_hash - vote = JSONField(electionalgs.EncryptedVote) + # the actual encrypted vote + vote = JSONField(datatypes.LDObject, + deserialization_params = {'type_hint' : 'legacy/EncryptedVote'}) # cache the hash of the vote vote_hash = models.CharField(max_length=100) @@ -866,20 +883,31 @@ class Trustee(HeliosModel): secret = models.CharField(max_length=100) # public key - public_key = JSONField(algs.EGPublicKey, null=True) + public_key = JSONField(datatypes.LDObject, + deserialization_params = {'type_hint': 'legacy/EGPublicKey'}, + null=True) public_key_hash = models.CharField(max_length=100) # secret key # if the secret key is present, this means # Helios is playing the role of the trustee. - secret_key = JSONField(algs.EGSecretKey, null=True) + secret_key = JSONField(datatypes.LDObject, + deserialization_params = {'type_hint': 'legacy/EGSecretKey'}, + null=True) # proof of knowledge of secret key - pok = JSONField(algs.DLogProof, null=True) + pok = JSONField(datatypes.LDObject, + deserialization_params = {'type_hint': 'legacy/DLogProof'}, + null=True) # decryption factors - decryption_factors = JSONField(null=True) - decryption_proofs = JSONField(null=True) + decryption_factors = JSONField(datatypes.LDObject, + deserialization_params = {'type_hint' : datatypes.arrayOf(datatypes.arrayOf('core/BigInteger'))}, + null=True) + + decryption_proofs = JSONField(datatypes.LDObject, + deserialization_params = {'type_hint' : datatypes.arrayOf(datatypes.arrayOf('legacy/DLogProof'))}, + null=True) def save(self, *args, **kwargs): """ diff --git a/helios/tests.py b/helios/tests.py index 7838baf3262f1d9564332e768b4cdf07ad485c66..9837271ca70ba289431bc8fc57c12e18d1eb86b8 100644 --- a/helios/tests.py +++ b/helios/tests.py @@ -226,6 +226,15 @@ class DatatypeTests(TestCase): def test_instantiate(self): ld_obj = datatypes.LDObject.instantiate(self.election.get_helios_trustee(), '2011/01/Trustee') foo = ld_obj.serialize() + + def test_from_dict(self): + ld_obj = datatypes.LDObject.fromDict({ + 'y' : '1234', + 'p' : '23434', + 'g' : '2343243242', + 'q' : '2343242343434'}, type_hint = 'pkc/elgamal/PublicKey') + + import pdb; pdb.set_trace()