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()