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]