diff --git a/.gitignore b/.gitignore
index 681947f7f..8db764f4e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,7 +3,8 @@ deploy-latest.sh
.DS_Store
*~
media/*
-venv
+venv*
celerybeat-*
env.sh
-.cache
\ No newline at end of file
+.cache
+.idea/
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index be482e4e4..476acaf46 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,14 +1,46 @@
-sudo: false
language: python
-python:
- - "2.7"
-# command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors
+os: linux
+
+jobs:
+ include:
+ - python: "3.7"
+ dist: xenial
+ addons:
+ postgresql: "9.5"
+ - python: "3.7"
+ dist: xenial
+ addons:
+ postgresql: "9.6"
+ - python: "3.7"
+ dist: xenial
+ addons:
+ postgresql: "10"
+ - python: "3.8"
+ dist: bionic
+ addons:
+ postgresql: "9.5"
+ - python: "3.8"
+ dist: bionic
+ addons:
+ postgresql: "9.6"
+ - python: "3.8"
+ dist: bionic
+ addons:
+ postgresql: "10"
+ - python: "3.8"
+ dist: bionic
+ addons:
+ postgresql: "11"
+
+before_install:
+ - export BOTO_CONFIG=/dev/null
+
install:
- - pip install setuptools==24.3.1
- - pip install -r requirements.txt
-# command to run tests, e.g. python setup.py test
-script: "python manage.py test"
-addons:
- postgresql: "9.3"
+ - pip3 install --upgrade pip
+ - pip3 install -r requirements.txt
+ - pip3 freeze
+
before_script:
- psql -c 'create database helios;' -U postgres
+
+script: "python3 -Wall manage.py test -v 2"
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 61ac19608..a0c88cf51 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -7,3 +7,6 @@ Significant contributors:
- Olivier de Marneffe
- Emily Stark, Mike Hamburg, Tom Wu, and Dan Boneh for SJCL and integration of javascript crypto.
- Nicholas Chang-Fong and Aleksander Essex for security reports and fixes.
+- Shirley Chaves
+- Marco Ciotola
+- Lucas Araujo
diff --git a/INSTALL.md b/INSTALL.md
index fdb0ae10f..c14d97659 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -1,4 +1,8 @@
-* install PostgreSQL 8.3+
+* install PostgreSQL 9.5+
+
+* install Rabbit MQ
+ This is needed for celery to work, which does background processing such as
+ the processing of uploaded list-of-voter CSV files.
* make sure you have virtualenv installed:
http://www.virtualenv.org/en/latest/
@@ -7,10 +11,22 @@ http://www.virtualenv.org/en/latest/
* cd into the helios-server directory
-* create a virtualenv:
+* install Python3.6 including dev, pip, and venv
+
+```
+sudo apt install python3.6 python3.6-venv python3.6-pip python3.6-venv
+```
+
+* create a virtualenv
+
+```
+python3.6 -m venv $(pwd)/venv
+```
+
+* you'll also need Postgres dev libraries. For example on Ubuntu:
```
-virtualenv venv
+sudo apt install libpq-dev
```
* activate virtual environment
@@ -45,6 +61,6 @@ python manage.py runserver
** set up oauth2 credentials as a web application, with your origin, e.g. https://myhelios.example.com, and your auth callback, which, based on our example, is https://myhelios.example.com/auth/after/
-** still in the developer console, enable the Google+ API.
+** still in the developer console, enable the Google+ API and Google People API.
-** set the GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET configuration variables accordingly.
\ No newline at end of file
+** set the GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET configuration variables accordingly.
diff --git a/Procfile b/Procfile
index 63d8d0cf6..04ed6cbca 100644
--- a/Procfile
+++ b/Procfile
@@ -1,2 +1,2 @@
web: gunicorn wsgi:application -b 0.0.0.0:$PORT -w 8
-worker: python manage.py celeryd -E -B --beat --concurrency=1
\ No newline at end of file
+worker: celery --app helios worker --events --beat --concurrency 1
\ No newline at end of file
diff --git a/README.md b/README.md
index 5c90a9c20..9a5519bfc 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,6 @@
Helios is an end-to-end verifiable voting system.
-![Travis Build Status](https://travis-ci.org/benadida/helios-server.svg?branch=master)
+[![Travis Build Status](https://travis-ci.org/benadida/helios-server.svg?branch=master)](https://travis-ci.org/benadida/helios-server)
[![Stories in Ready](https://badge.waffle.io/benadida/helios-server.png?label=ready&title=Ready)](https://waffle.io/benadida/helios-server)
diff --git a/extract-passwords-for-email.py b/extract-passwords-for-email.py
index d72705792..dc9ac9564 100644
--- a/extract-passwords-for-email.py
+++ b/extract-passwords-for-email.py
@@ -5,12 +5,16 @@
# python extract-passwords-for-email.py
#
-from django.core.management import setup_environ
-import settings, sys, csv
+import sys
-setup_environ(settings)
+import csv
+import django
+import os
-from helios.models import *
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
+django.setup()
+
+from helios.models import Election
election_uuid = sys.argv[1]
email = sys.argv[2]
diff --git a/helios/__init__.py b/helios/__init__.py
index 06f05140b..21f21bc59 100644
--- a/helios/__init__.py
+++ b/helios/__init__.py
@@ -1,7 +1,9 @@
-
from django.conf import settings
-from django.core.urlresolvers import reverse
-from helios.views import election_shortcut
+# This will make sure the app is always imported when
+# Django starts so that shared_task will use this app.
+from .celery_app import app as celery_app
+
+__all__ = ('celery_app', 'TEMPLATE_BASE', 'ADMIN_ONLY', 'VOTERS_UPLOAD', 'VOTERS_EMAIL',)
TEMPLATE_BASE = settings.HELIOS_TEMPLATE_BASE or "helios/templates/base.html"
diff --git a/helios/apps.py b/helios/apps.py
new file mode 100644
index 000000000..900974328
--- /dev/null
+++ b/helios/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+class HeliosConfig(AppConfig):
+ name = 'helios'
+ verbose_name = "Helios"
diff --git a/helios/celery_app.py b/helios/celery_app.py
new file mode 100644
index 000000000..89f0ecb54
--- /dev/null
+++ b/helios/celery_app.py
@@ -0,0 +1,21 @@
+import os
+
+# set the default Django settings module for the 'celery' program.
+from celery import Celery
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings')
+
+app = Celery()
+
+# Using a string here means the worker doesn't have to serialize
+# the configuration object to child processes.
+# - namespace='CELERY' means all celery-related configuration keys
+# should have a `CELERY_` prefix.
+app.config_from_object('django.conf:settings', namespace='CELERY')
+
+# Load task modules from all registered Django app configs.
+app.autodiscover_tasks()
+
+@app.task(bind=True)
+def debug_task(self):
+ print('Request: {0!r}'.format(self.request))
diff --git a/helios/crypto/algs.py b/helios/crypto/algs.py
index acac4f44b..b7439d9bd 100644
--- a/helios/crypto/algs.py
+++ b/helios/crypto/algs.py
@@ -7,157 +7,61 @@
ben@adida.net
"""
-import math, hashlib, logging
-import randpool, number
+import logging
-import numtheory
+from Crypto.Hash import SHA1
+from Crypto.Util import number
-# some utilities
-class Utils:
- RAND = randpool.RandomPool()
-
- @classmethod
- def random_seed(cls, data):
- cls.RAND.add_event(data)
-
- @classmethod
- def random_mpz(cls, n_bits):
- low = 2**(n_bits-1)
- high = low * 2
-
- # increment and find a prime
- # return randrange(low, high)
-
- return number.getRandomNumber(n_bits, cls.RAND.get_bytes)
-
- @classmethod
- def random_mpz_lt(cls, max):
- # return randrange(0, max)
- n_bits = int(math.floor(math.log(max, 2)))
- return (number.getRandomNumber(n_bits, cls.RAND.get_bytes) % max)
-
- @classmethod
- def random_prime(cls, n_bits):
- return number.getPrime(n_bits, cls.RAND.get_bytes)
-
- @classmethod
- def is_prime(cls, mpz):
- #return numtheory.miller_rabin(mpz)
- return number.isPrime(mpz)
-
- @classmethod
- def xgcd(cls, a, b):
- """
- Euclid's Extended GCD algorithm
- """
- mod = a%b
-
- if mod == 0:
- return 0,1
- else:
- x,y = cls.xgcd(b, mod)
- return y, x-(y*(a/b))
-
- @classmethod
- def inverse(cls, mpz, mod):
- # return cls.xgcd(mpz,mod)[0]
- return number.inverse(mpz, mod)
-
- @classmethod
- def random_safe_prime(cls, n_bits):
- p = None
- q = None
-
- while True:
- p = cls.random_prime(n_bits)
- q = (p-1)/2
- if cls.is_prime(q):
- return p
-
- @classmethod
- def random_special_prime(cls, q_n_bits, p_n_bits):
- p = None
- q = None
-
- z_n_bits = p_n_bits - q_n_bits
-
- q = cls.random_prime(q_n_bits)
-
- while True:
- z = cls.random_mpz(z_n_bits)
- p = q*z + 1
- if cls.is_prime(p):
- return p, q, z
+from helios.crypto.utils import random
+from helios.utils import to_json
class ElGamal:
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 = ElGamal()
-
- # 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
+ self.p = None
+ self.q = None
+ self.g = None
def generate_keypair(self):
- """
- generates a keypair in the setting
- """
+ """
+ generates a keypair in the setting
+ """
- keypair = EGKeyPair()
- keypair.generate(self.p, self.q, self.g)
+ keypair = EGKeyPair()
+ keypair.generate(self.p, self.q, self.g)
- return keypair
+ return keypair
def toJSONDict(self):
- return {'p': str(self.p), 'q': str(self.q), 'g': str(self.g)}
+ return {'p': str(self.p), 'q': str(self.q), 'g': str(self.g)}
@classmethod
def fromJSONDict(cls, d):
- eg = cls()
- eg.p = int(d['p'])
- eg.q = int(d['q'])
- eg.g = int(d['g'])
- return eg
+ eg = cls()
+ eg.p = int(d['p'])
+ eg.q = int(d['q'])
+ eg.g = int(d['g'])
+ return eg
+
class EGKeyPair:
def __init__(self):
- self.pk = EGPublicKey()
- self.sk = EGSecretKey()
+ self.pk = EGPublicKey()
+ self.sk = EGSecretKey()
def generate(self, p, q, g):
- """
- Generate an ElGamal keypair
- """
- self.pk.g = g
- self.pk.p = p
- self.pk.q = q
+ """
+ Generate an ElGamal keypair
+ """
+ self.pk.g = g
+ self.pk.p = p
+ self.pk.q = q
+
+ self.sk.x = random.mpz_lt(q)
+ self.pk.y = pow(g, self.sk.x, p)
- self.sk.x = Utils.random_mpz_lt(q)
- self.pk.y = pow(g, self.sk.x, p)
+ self.sk.pk = self.pk
- self.sk.pk = self.pk
class EGPublicKey:
def __init__(self):
@@ -166,7 +70,7 @@ def __init__(self):
self.g = None
self.q = None
- def encrypt_with_r(self, plaintext, r, encode_message= False):
+ def encrypt_with_r(self, plaintext, r, encode_message=False):
"""
expecting plaintext.m to be a big integer
"""
@@ -175,13 +79,13 @@ def encrypt_with_r(self, plaintext, r, encode_message= False):
# 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
+ y = plaintext.m + 1
+ if pow(y, self.q, self.p) == 1:
+ m = y
+ else:
+ m = -y % self.p
else:
- m = plaintext.m
+ m = plaintext.m
ciphertext.alpha = pow(self.g, r, self.p)
ciphertext.beta = (m * pow(self.y, r, self.p)) % self.p
@@ -192,7 +96,7 @@ def encrypt_return_r(self, plaintext):
"""
Encrypt a plaintext and return the randomness just generated and used.
"""
- r = Utils.random_mpz_lt(self.q)
+ r = random.mpz_lt(self.q)
ciphertext = self.encrypt_with_r(plaintext, r)
return [ciphertext, r]
@@ -207,70 +111,69 @@ def to_dict(self):
"""
Serialize to dictionary.
"""
- return {'y' : str(self.y), 'p' : str(self.p), 'g' : str(self.g) , 'q' : str(self.q)}
+ return {'y': str(self.y), 'p': str(self.p), 'g': str(self.g), 'q': str(self.q)}
toJSONDict = to_dict
# quick hack FIXME
def toJSON(self):
- import utils
- return utils.to_json(self.toJSONDict())
+ return to_json(self.toJSONDict())
- def __mul__(self,other):
- if other == 0 or other == 1:
- return self
+ 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")
+ # 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 = EGPublicKey()
- result.p = self.p
- result.q = self.q
- result.g = self.g
- result.y = (self.y * other.y) % result.p
- return result
+ result = EGPublicKey()
+ 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
+ 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
+ expected_challenge = challenge_generator(dlog_proof.commitment) % self.q
- return ((left_side == right_side) and (dlog_proof.challenge == expected_challenge))
+ return (left_side == right_side) and (dlog_proof.challenge == expected_challenge)
def validate_pk_params(self):
- # check primality of p
- if not number.isPrime(self.p):
- raise Exception("p is not prime.")
+ # check primality of p
+ if not number.isPrime(self.p):
+ raise Exception("p is not prime.")
- # check length of p
- if not (number.size(self.p) >= 2048):
- raise Exception("p of insufficient length. Should be 2048 bits or greater.")
+ # check length of p
+ if not (number.size(self.p) >= 2048):
+ raise Exception("p of insufficient length. Should be 2048 bits or greater.")
- # check primality of q
- if not number.isPrime(self.q):
- raise Exception("q is not prime.")
+ # check primality of q
+ if not number.isPrime(self.q):
+ raise Exception("q is not prime.")
- # check length of q
- if not (number.size(self.q) >= 256):
- raise Exception("q of insufficient length. Should be 256 bits or greater.")
+ # check length of q
+ if not (number.size(self.q) >= 256):
+ raise Exception("q of insufficient length. Should be 256 bits or greater.")
- if (pow(self.g,self.q,self.p)!=1):
- raise Exception("g does not generate subgroup of order q.")
+ if pow(self.g, self.q, self.p) != 1:
+ raise Exception("g does not generate subgroup of order q.")
- if not (1 < self.g < self.p-1):
- raise Exception("g out of range.")
+ if not (1 < self.g < self.p - 1):
+ raise Exception("g out of range.")
- if not (1 < self.y < self.p-1):
- raise Exception("y out of range.")
+ if not (1 < self.y < self.p - 1):
+ raise Exception("y out of range.")
- if (pow(self.y,self.q,self.p)!=1):
- raise Exception("g does not generate proper group.")
+ if pow(self.y, self.q, self.p) != 1:
+ raise Exception("g does not generate proper group.")
@classmethod
def from_dict(cls, d):
@@ -284,14 +187,15 @@ def from_dict(cls, d):
pk.q = int(d['q'])
try:
- pk.validate_pk_params()
+ pk.validate_pk_params()
except Exception as e:
- raise
+ raise e
return pk
fromJSONDict = from_dict
+
class EGSecretKey:
def __init__(self):
self.x = None
@@ -317,25 +221,25 @@ def decryption_factor_and_proof(self, ciphertext, challenge_generator=None):
return dec_factor, proof
- def decrypt(self, ciphertext, dec_factor = None, decode_m=False):
+ 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
+ m = (number.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
+ # get m back from the q-order subgroup
+ if m < self.pk.q:
+ y = m
+ else:
+ y = -m % self.pk.p
- return EGPlaintext(y-1, self.pk)
+ return EGPlaintext(y - 1, self.pk)
else:
- return EGPlaintext(m, self.pk)
+ return EGPlaintext(m, self.pk)
def prove_decryption(self, ciphertext):
"""
@@ -350,66 +254,66 @@ def prove_decryption(self, ciphertext):
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
+ m = (number.inverse(pow(ciphertext.alpha, self.x, self.pk.p), self.pk.p) * ciphertext.beta) % self.pk.p
+ beta_over_m = (ciphertext.beta * number.inverse(m, self.pk.p)) % self.pk.p
# pick a random w
- w = Utils.random_mpz_lt(self.pk.q)
+ w = 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)
+ c = int(SHA1.new(bytes(str(a) + "," + str(b), 'utf-8')).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)
- }
+ 'commitment': {'A': str(a), 'B': str(b)},
+ 'challenge': str(c),
+ 'response': str(t)
+ }
def to_dict(self):
- return {'x' : str(self.x), 'public_key' : self.pk.to_dict()}
+ return {'x': str(self.x), 'public_key': self.pk.to_dict()}
toJSONDict = to_dict
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)
+ """
+ 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 = 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)
@classmethod
def from_dict(cls, d):
if not d:
- return None
+ return None
sk = cls()
sk.x = int(d['x'])
- if d.has_key('public_key'):
- sk.pk = EGPublicKey.from_dict(d['public_key'])
+ if 'public_key' in d:
+ sk.pk = EGPublicKey.from_dict(d['public_key'])
else:
- sk.pk = None
+ sk.pk = None
return sk
fromJSONDict = from_dict
+
class EGPlaintext:
- def __init__(self, m = None, pk = None):
+ def __init__(self, m=None, pk=None):
self.m = m
self.pk = pk
def to_dict(self):
- return {'m' : self.m}
+ return {'m': self.m}
@classmethod
def from_dict(cls, d):
@@ -424,17 +328,17 @@ def __init__(self, alpha=None, beta=None, pk=None):
self.alpha = alpha
self.beta = beta
- def __mul__(self,other):
+ def __mul__(self, other):
"""
Homomorphic Multiplication of ciphertexts.
"""
- if type(other) == int and (other == 0 or other == 1):
- return self
+ if isinstance(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!')
+ logging.info(self.pk)
+ logging.info(other.pk)
+ raise Exception('different PKs!')
new = EGCiphertext()
@@ -460,7 +364,7 @@ def reenc_return_r(self):
"""
Reencryption with fresh randomness, which is returned.
"""
- r = Utils.random_mpz_lt(self.pk.q)
+ r = random.mpz_lt(self.pk.q)
new_c = self.reenc_with_r(r)
return [new_c, r]
@@ -471,189 +375,195 @@ def reenc(self):
return self.reenc_return_r()[0]
def __eq__(self, other):
- """
- Check for ciphertext equality.
- """
- if other == None:
- return False
+ """
+ Check for ciphertext equality.
+ """
+ if other is None:
+ return False
- return (self.alpha == other.alpha and self.beta == other.beta)
+ 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)
+ """
+ Generate the disjunctive encryption proof of encryption
+ """
+ # random W
+ w = random.mpz_lt(self.pk.q)
- # build the proof
- proof = EGZKProof()
+ # build the proof
+ proof = EGZKProof()
- # 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)
+ # 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);
+ # generate challenge
+ proof.challenge = challenge_generator(proof.commitment)
- # Compute response = w + randomness * challenge
- proof.response = (w + (randomness * proof.challenge)) % self.pk.q;
+ # Compute response = w + randomness * challenge
+ proof.response = (w + (randomness * proof.challenge)) % self.pk.q
- return proof;
+ 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)
+ # generate a random challenge if not provided
+ if not challenge:
+ challenge = random.mpz_lt(self.pk.q)
- proof = EGZKProof()
- proof.challenge = challenge
+ proof = EGZKProof()
+ 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
+ # compute beta/plaintext, the completion of the DH tuple
+ beta_over_plaintext = (self.beta * number.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);
+ # random response, does not even need to depend on the challenge
+ proof.response = 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
+ # now we compute A and B
+ proof.commitment['A'] = (number.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'] = (number.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
+ 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.
+ # 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]
+ proofs = [None for _ 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])
+ # 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] = EGZKProof()
- proofs[real_index].commitment = commitment
+ # 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] = EGZKProof()
+ 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);
+ # 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
+ # 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
+ # 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)
+ # 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
+ # set the real proof
+ proofs[real_index] = real_proof
- return EGZKDisjunctiveProof(proofs)
+ return EGZKDisjunctiveProof(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 A, B are in the correct group
- if not (pow(proof.commitment['A'],self.pk.q,self.pk.p)==1 and pow(proof.commitment['B'],self.pk.q,self.pk.p)==1):
- return False
+ """
+ Checks for the DDH tuple g, y, alpha, beta/plaintext.
+ (PoK of randomness r.)
+
+ Proof contains commitment = {A, B}, challenge, response
+ """
+ # check that A, B are in the correct group
+ if not (pow(proof.commitment['A'], self.pk.q, self.pk.p) == 1 and pow(proof.commitment['B'], self.pk.q,
+ self.pk.p) == 1):
+ return False
- # 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 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))
+ # check that y^response = B * (beta/m)^challenge
+ beta_over_m = (self.beta * number.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)
+ # 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.
+ """
+ plaintexts and proofs are all lists of equal length, with matching.
- overall_challenge is what all of the challenges combined should yield.
- """
- if len(plaintexts) != len(proof.proofs):
- print("bad number of proofs (expected %s, found %s)" % (len(plaintexts), len(proof.proofs)))
- return False
+ overall_challenge is what all of the challenges combined should yield.
+ """
+ if len(plaintexts) != len(proof.proofs):
+ print("bad number of proofs (expected %s, found %s)" % (len(plaintexts), len(proof.proofs)))
+ return False
- 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
+ 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")
+ # 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))
+ # 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
+ """
+ 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
+ """
+ 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
+ """
+ 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 * number.inverse(dec_factor, public_key.p)) % public_key.p
- return running_decryption
+ return running_decryption
def check_group_membership(self, pk):
- """
- checks to see if an ElGamal element belongs to the group in the pk
- """
- if not (1 < self.alpha < pk.p-1):
- return False
-
- elif not (1 < self.beta < pk.p-1):
- return False
+ """
+ checks to see if an ElGamal element belongs to the group in the pk
+ """
+ if not (1 < self.alpha < pk.p - 1):
+ return False
- elif (pow(self.alpha, pk.q, pk.p)!=1):
- return False
+ elif not (1 < self.beta < pk.p - 1):
+ return False
- elif (pow(self.beta, pk.q, pk.p)!=1):
- return False
+ elif pow(self.alpha, pk.q, pk.p) != 1:
+ return False
- else:
- return True
+ elif pow(self.beta, pk.q, pk.p) != 1:
+ return False
+ else:
+ return True
def to_dict(self):
return {'alpha': str(self.alpha), 'beta': str(self.beta)}
- toJSONDict= to_dict
+ toJSONDict = to_dict
def to_string(self):
return "%s,%s" % (self.alpha, self.beta)
@classmethod
- def from_dict(cls, d, pk = None):
+ def from_dict(cls, d, pk=None):
result = cls()
result.alpha = int(d['alpha'])
result.beta = int(d['beta'])
@@ -668,127 +578,134 @@ def from_string(cls, str):
expects alpha,beta
"""
split = str.split(",")
- return cls.from_dict({'alpha' : split[0], 'beta' : split[1]})
+ return cls.from_dict({'alpha': split[0], 'beta': split[1]})
+
class EGZKProof(object):
- def __init__(self):
- self.commitment = {'A':None, 'B':None}
- self.challenge = None
- self.response = None
+ 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
- """
+ @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)
+ # generate random w
+ w = random.mpz_lt(q)
- # create proof instance
- proof = cls()
+ # 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)
+ # 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)
+ # get challenge
+ proof.challenge = challenge_generator(proof.commitment)
- # compute response
- proof.response = (w + (x * proof.challenge)) % q
+ # compute response
+ proof.response = (w + (x * proof.challenge)) % q
- # return proof
- return proof
+ # return proof
+ return proof
- @classmethod
- def from_dict(cls, d):
- p = cls()
- p.commitment = {'A': int(d['commitment']['A']), 'B': int(d['commitment']['B'])}
- p.challenge = int(d['challenge'])
- p.response = int(d['response'])
- return p
+ @classmethod
+ def from_dict(cls, d):
+ p = cls()
+ p.commitment = {'A': int(d['commitment']['A']), 'B': int(d['commitment']['B'])}
+ p.challenge = int(d['challenge'])
+ p.response = int(d['response'])
+ return p
- fromJSONDict = from_dict
+ fromJSONDict = from_dict
+
+ def to_dict(self):
+ return {
+ 'commitment': {'A': str(self.commitment['A']), 'B': str(self.commitment['B'])},
+ 'challenge': str(self.challenge),
+ 'response': str(self.response)
+ }
- def to_dict(self):
- return {
- 'commitment' : {'A' : str(self.commitment['A']), 'B' : str(self.commitment['B'])},
- 'challenge': str(self.challenge),
- 'response': str(self.response)
- }
+ toJSONDict = to_dict
- def verify(self, little_g, little_h, big_g, big_h, p, q, challenge_generator=None):
- """
- Verify a DH tuple proof
- """
- # check that A, B are in the correct group
- if not (pow(proof.commitment['A'],self.pk.q,self.pk.p)==1 and pow(proof.commitment['B'],self.pk.q,self.pk.p)==1):
- return False
+ def verify(self, little_g, little_h, big_g, big_h, p, q, challenge_generator=None):
+ """
+ Verify a DH tuple proof
+ """
+ # check that A, B are in the correct group
+ if not (pow(self.commitment['A'], self.pk.q, self.pk.p) == 1
+ and pow(self.commitment['B'], self.pk.q, self.pk.p) == 1):
+ return False
- # 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_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 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
+ # check the challenge?
+ third_check = True
- if challenge_generator:
- third_check = (self.challenge == challenge_generator(self.commitment))
+ if challenge_generator:
+ third_check = (self.challenge == challenge_generator(self.commitment))
- return (first_check and second_check and third_check)
+ return first_check and second_check and third_check
- toJSONDict = to_dict
class EGZKDisjunctiveProof:
- def __init__(self, proofs = None):
- self.proofs = proofs
+ def __init__(self, proofs=None):
+ self.proofs = proofs
- @classmethod
- def from_dict(cls, d):
- dp = cls()
- dp.proofs = [EGZKProof.from_dict(p) for p in d]
- return dp
+ @classmethod
+ def from_dict(cls, d):
+ dp = cls()
+ dp.proofs = [EGZKProof.from_dict(p) for p in d]
+ return dp
- def to_dict(self):
- return [p.to_dict() for p in self.proofs]
+ def to_dict(self):
+ return [p.to_dict() for p in self.proofs]
+
+ toJSONDict = to_dict
- toJSONDict = to_dict
class DLogProof(object):
- def __init__(self, commitment, challenge, response):
- self.commitment = commitment
- self.challenge = challenge
- self.response = response
+ def __init__(self, commitment, challenge, response):
+ self.commitment = commitment
+ self.challenge = challenge
+ self.response = response
- def to_dict(self):
- return {'challenge': str(self.challenge), 'commitment': str(self.commitment), 'response' : str(self.response)}
+ def to_dict(self):
+ return {'challenge': str(self.challenge), 'commitment': str(self.commitment), 'response': str(self.response)}
- toJSONDict = to_dict
+ toJSONDict = to_dict
- @classmethod
- def from_dict(cls, d):
- dlp = cls(int(d['commitment']), int(d['challenge']), int(d['response']))
- return dlp
+ @classmethod
+ def from_dict(cls, d):
+ dlp = cls(int(d['commitment']), int(d['challenge']), int(d['response']))
+ return dlp
+
+ fromJSONDict = from_dict
- fromJSONDict = from_dict
def EG_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']))
+ 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(SHA1.new(bytes(string_to_hash, 'utf-8')).hexdigest(), 16)
- 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 EG_fiatshamir_challenge_generator(commitment):
- return EG_disjunctive_challenge_generator([commitment])
+ return EG_disjunctive_challenge_generator([commitment])
+
def DLog_challenge_generator(commitment):
- string_to_hash = str(commitment)
- return int(hashlib.sha1(string_to_hash).hexdigest(),16)
+ string_to_hash = str(commitment)
+ return int(SHA1.new(bytes(string_to_hash, 'utf-8')).hexdigest(), 16)
diff --git a/helios/crypto/electionalgs.py b/helios/crypto/electionalgs.py
index c67714043..de833f8da 100644
--- a/helios/crypto/electionalgs.py
+++ b/helios/crypto/electionalgs.py
@@ -4,778 +4,807 @@
Ben Adida
2008-08-30
"""
-
-import algs
-import logging
-import utils
-import uuid
import datetime
+import uuid
+import logging
-class HeliosObject(object):
- """
- A base class to ease serialization and de-serialization
- crypto objects are kept as full-blown crypto objects, serialized to jsonobjects on the way out
- and deserialized from jsonobjects on the way in
- """
- FIELDS = []
- JSON_FIELDS = None
-
- def __init__(self, **kwargs):
- self.set_from_args(**kwargs)
-
- # generate uuid if need be
- if 'uuid' in self.FIELDS and (not hasattr(self, 'uuid') or self.uuid == None):
- self.uuid = str(uuid.uuid4())
-
- 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)
- else:
- setattr(self, f, None)
-
- def set_from_other_object(self, o):
- for f in self.FIELDS:
- if hasattr(o, f):
- setattr(self, f, self.process_value_in(f, getattr(o,f)))
- else:
- setattr(self, f, None)
-
- def toJSON(self):
- return utils.to_json(self.toJSONDict())
-
- def toJSONDict(self, alternate_fields=None):
- val = {}
- for f in (alternate_fields or self.JSON_FIELDS or self.FIELDS):
- val[f] = self.process_value_out(f, getattr(self, f))
- return val
-
- @classmethod
- def fromJSONDict(cls, d):
- # go through the keys and fix them
- new_d = {}
- for k in d.keys():
- new_d[str(k)] = d[k]
-
- return cls(**new_d)
-
- @classmethod
- def fromOtherObject(cls, o):
- obj = cls()
- obj.set_from_other_object(o)
- return obj
-
- def toOtherObject(self, o):
- for f in self.FIELDS:
- # FIXME: why isn't this working?
- if hasattr(o, f):
- # BIG HAMMER
- try:
- setattr(o, f, self.process_value_out(f, getattr(self,f)))
- except:
- pass
-
- @property
- def hash(self):
- s = utils.to_json(self.toJSONDict())
- return utils.hash_b64(s)
-
- def process_value_in(self, field_name, field_value):
- """
- process some fields on the way into the object
- """
- if field_value == None:
- return None
-
- val = self._process_value_in(field_name, field_value)
- if val != None:
- return val
- else:
- return field_value
+from helios.utils import to_json
+from . import algs
+from . import utils
- def _process_value_in(self, field_name, field_value):
- return None
- def process_value_out(self, field_name, field_value):
+class HeliosObject(object):
"""
- process some fields on the way out of the object
+ A base class to ease serialization and de-serialization
+ crypto objects are kept as full-blown crypto objects, serialized to jsonobjects on the way out
+ and deserialized from jsonobjects on the way in
"""
- if field_value == None:
- return None
-
- val = self._process_value_out(field_name, field_value)
- if val != None:
- return val
- else:
- return field_value
-
- def _process_value_out(self, field_name, field_value):
- return None
+ FIELDS = []
+ JSON_FIELDS = None
+
+ def __init__(self, **kwargs):
+ self.set_from_args(**kwargs)
+
+ # generate uuid if need be
+ if 'uuid' in self.FIELDS and (not hasattr(self, 'uuid') or self.uuid is None):
+ self.uuid = str(uuid.uuid4())
+
+ def set_from_args(self, **kwargs):
+ for f in self.FIELDS:
+ if f in kwargs:
+ new_val = self.process_value_in(f, kwargs[f])
+ setattr(self, f, new_val)
+ else:
+ setattr(self, f, None)
+
+ def set_from_other_object(self, o):
+ for f in self.FIELDS:
+ if hasattr(o, f):
+ setattr(self, f, self.process_value_in(f, getattr(o, f)))
+ else:
+ setattr(self, f, None)
+
+ def toJSON(self):
+ return to_json(self.toJSONDict())
+
+ def toJSONDict(self, alternate_fields=None):
+ val = {}
+ for f in (alternate_fields or self.JSON_FIELDS or self.FIELDS):
+ val[f] = self.process_value_out(f, getattr(self, f))
+ return val
+
+ @classmethod
+ def fromJSONDict(cls, d):
+ # go through the keys and fix them
+ new_d = {}
+ for k in list(d.keys()):
+ new_d[str(k)] = d[k]
+
+ return cls(**new_d)
+
+ @classmethod
+ def fromOtherObject(cls, o):
+ obj = cls()
+ obj.set_from_other_object(o)
+ return obj
+
+ def toOtherObject(self, o):
+ for f in self.FIELDS:
+ # FIXME: why isn't this working?
+ if hasattr(o, f):
+ # BIG HAMMER
+ try:
+ setattr(o, f, self.process_value_out(f, getattr(self, f)))
+ except:
+ pass
+
+ @property
+ def hash(self):
+ s = to_json(self.toJSONDict())
+ return utils.hash_b64(s)
+
+ def process_value_in(self, field_name, field_value):
+ """
+ process some fields on the way into the object
+ """
+ if field_value is None:
+ return None
+
+ val = self._process_value_in(field_name, field_value)
+ if val is not None:
+ return val
+ else:
+ return field_value
+
+ def _process_value_in(self, field_name, field_value):
+ return None
+
+ def process_value_out(self, field_name, field_value):
+ """
+ process some fields on the way out of the object
+ """
+ if field_value is None:
+ return None
+
+ val = self._process_value_out(field_name, field_value)
+ if val is not None:
+ return val
+ else:
+ return field_value
+
+ def _process_value_out(self, field_name, field_value):
+ return None
+
+ def __eq__(self, other):
+ if not hasattr(self, 'uuid'):
+ return super(HeliosObject, self) == other
+
+ return other is not None and self.uuid == other.uuid
- def __eq__(self, other):
- if not hasattr(self, 'uuid'):
- return super(HeliosObject,self) == other
-
- return other != None and self.uuid == other.uuid
class EncryptedAnswer(HeliosObject):
- """
- An encrypted answer to a single election question
- """
-
- FIELDS = ['choices', 'individual_proofs', 'overall_proof', 'randomness', 'answer']
-
- # FIXME: remove this constructor and use only named-var constructor from HeliosObject
- def __init__(self, choices=None, individual_proofs=None, overall_proof=None, randomness=None, answer=None):
- self.choices = choices
- self.individual_proofs = individual_proofs
- self.overall_proof = overall_proof
- self.randomness = randomness
- self.answer = answer
-
- @classmethod
- def generate_plaintexts(cls, pk, min=0, max=1):
- plaintexts = []
- running_product = 1
-
- # run the product up to the min
- for i in range(max+1):
- # if we're in the range, add it to the array
- if i >= min:
- plaintexts.append(algs.EGPlaintext(running_product, pk))
-
- # next value in running product
- running_product = (running_product * pk.g) % pk.p
-
- return plaintexts
-
- def verify_plaintexts_and_randomness(self, pk):
"""
- this applies only if the explicit answers and randomness factors are given
- we do not verify the proofs here, that is the verify() method
+ An encrypted answer to a single election question
"""
- if not hasattr(self, 'answer'):
- return False
-
- for choice_num in range(len(self.choices)):
- choice = self.choices[choice_num]
- choice.pk = pk
- # redo the encryption
- # WORK HERE (paste from below encryption)
+ FIELDS = ['choices', 'individual_proofs', 'overall_proof', 'randomness', 'answer']
- return False
+ # FIXME: remove this constructor and use only named-var constructor from HeliosObject
+ def __init__(self, choices=None, individual_proofs=None, overall_proof=None, randomness=None, answer=None):
+ self.choices = choices
+ self.individual_proofs = individual_proofs
+ self.overall_proof = overall_proof
+ self.randomness = randomness
+ self.answer = answer
- def verify(self, pk, min=0, max=1):
- possible_plaintexts = self.generate_plaintexts(pk)
- homomorphic_sum = 0
+ @classmethod
+ def generate_plaintexts(cls, pk, min=0, max=1):
+ plaintexts = []
+ running_product = 1
- for choice_num in range(len(self.choices)):
- choice = self.choices[choice_num]
- choice.pk = pk
- individual_proof = self.individual_proofs[choice_num]
+ # run the product up to the min
+ for i in range(max + 1):
+ # if we're in the range, add it to the array
+ if i >= min:
+ plaintexts.append(algs.EGPlaintext(running_product, pk))
- # verify that elements belong to the proper group
- if not choice.check_group_membership(pk):
- return False
-
- # verify the proof on the encryption of that choice
- if not choice.verify_disjunctive_encryption_proof(possible_plaintexts, individual_proof, algs.EG_disjunctive_challenge_generator):
- return False
+ # next value in running product
+ running_product = (running_product * pk.g) % pk.p
- # compute homomorphic sum if needed
- if max != None:
- homomorphic_sum = choice * homomorphic_sum
+ return plaintexts
- if max != None:
- # determine possible plaintexts for the sum
- sum_possible_plaintexts = self.generate_plaintexts(pk, min=min, max=max)
+ def verify_plaintexts_and_randomness(self, pk):
+ """
+ this applies only if the explicit answers and randomness factors are given
+ we do not verify the proofs here, that is the verify() method
+ """
+ if not hasattr(self, 'answer'):
+ return False
- # verify the sum
- return homomorphic_sum.verify_disjunctive_encryption_proof(sum_possible_plaintexts, self.overall_proof, algs.EG_disjunctive_challenge_generator)
- else:
- # approval voting, no need for overall proof verification
- return True
+ for choice_num in range(len(self.choices)):
+ choice = self.choices[choice_num]
+ choice.pk = pk
- def toJSONDict(self, with_randomness=False):
- value = {
- 'choices': [c.to_dict() for c in self.choices],
- 'individual_proofs' : [p.to_dict() for p in self.individual_proofs]
- }
+ # redo the encryption
+ # WORK HERE (paste from below encryption)
- if self.overall_proof:
- value['overall_proof'] = self.overall_proof.to_dict()
- else:
- value['overall_proof'] = None
-
- if with_randomness:
- value['randomness'] = [str(r) for r in self.randomness]
- value['answer'] = self.answer
-
- return value
-
- @classmethod
- def fromJSONDict(cls, d, pk=None):
- ea = cls()
-
- ea.choices = [algs.EGCiphertext.from_dict(c, pk) for c in d['choices']]
- ea.individual_proofs = [algs.EGZKDisjunctiveProof.from_dict(p) for p in d['individual_proofs']]
-
- if d['overall_proof']:
- ea.overall_proof = algs.EGZKDisjunctiveProof.from_dict(d['overall_proof'])
- else:
- ea.overall_proof = None
+ return False
- if d.has_key('randomness'):
- ea.randomness = [int(r) for r in d['randomness']]
- ea.answer = d['answer']
+ def verify(self, pk, min=0, max=1):
+ possible_plaintexts = self.generate_plaintexts(pk)
+ homomorphic_sum = 0
+
+ for choice_num in range(len(self.choices)):
+ choice = self.choices[choice_num]
+ choice.pk = pk
+ individual_proof = self.individual_proofs[choice_num]
+
+ # verify that elements belong to the proper group
+ if not choice.check_group_membership(pk):
+ return False
+
+ # verify the proof on the encryption of that choice
+ if not choice.verify_disjunctive_encryption_proof(possible_plaintexts, individual_proof,
+ algs.EG_disjunctive_challenge_generator):
+ return False
+
+ # compute homomorphic sum if needed
+ if max is not None:
+ homomorphic_sum = choice * homomorphic_sum
+
+ if max is not None:
+ # determine possible plaintexts for the sum
+ sum_possible_plaintexts = self.generate_plaintexts(pk, min=min, max=max)
+
+ # verify the sum
+ return homomorphic_sum.verify_disjunctive_encryption_proof(sum_possible_plaintexts, self.overall_proof,
+ algs.EG_disjunctive_challenge_generator)
+ else:
+ # approval voting, no need for overall proof verification
+ return True
+
+ def toJSONDict(self, with_randomness=False):
+ value = {
+ 'choices': [c.to_dict() for c in self.choices],
+ 'individual_proofs': [p.to_dict() for p in self.individual_proofs]
+ }
+
+ if self.overall_proof:
+ value['overall_proof'] = self.overall_proof.to_dict()
+ else:
+ value['overall_proof'] = None
+
+ if with_randomness:
+ value['randomness'] = [str(r) for r in self.randomness]
+ value['answer'] = self.answer
+
+ return value
+
+ @classmethod
+ def fromJSONDict(cls, d, pk=None):
+ ea = cls()
+
+ ea.choices = [algs.EGCiphertext.from_dict(c, pk) for c in d['choices']]
+ ea.individual_proofs = [algs.EGZKDisjunctiveProof.from_dict(p) for p in d['individual_proofs']]
+
+ if d['overall_proof']:
+ ea.overall_proof = algs.EGZKDisjunctiveProof.from_dict(d['overall_proof'])
+ else:
+ ea.overall_proof = None
+
+ if 'randomness' in d:
+ ea.randomness = [int(r) for r in d['randomness']]
+ ea.answer = d['answer']
+
+ return ea
+
+ @classmethod
+ def fromElectionAndAnswer(cls, election, question_num, answer_indexes):
+ """
+ Given an election, a question number, and a list of answers to that question
+ in the form of an array of 0-based indexes into the answer array,
+ produce an EncryptedAnswer that works.
+ """
+ question = election.questions[question_num]
+ answers = question['answers']
+ pk = election.public_key
+
+ # initialize choices, individual proofs, randomness and overall proof
+ choices = [None for _ in range(len(answers))]
+ individual_proofs = [None for _ in range(len(answers))]
+ randomness = [None for _ in range(len(answers))]
+
+ # possible plaintexts [0, 1]
+ plaintexts = cls.generate_plaintexts(pk)
+
+ # keep track of number of options selected.
+ num_selected_answers = 0
+
+ # homomorphic sum of all
+ homomorphic_sum = 0
+ randomness_sum = 0
+
+ # min and max for number of answers, useful later
+ min_answers = 0
+ if 'min' in question:
+ min_answers = question['min']
+ max_answers = question['max']
+
+ # go through each possible answer and encrypt either a g^0 or a g^1.
+ for answer_num in range(len(answers)):
+ plaintext_index = 0
+
+ # assuming a list of answers
+ if answer_num in answer_indexes:
+ plaintext_index = 1
+ num_selected_answers += 1
+
+ # randomness and encryption
+ randomness[answer_num] = utils.random.mpz_lt(pk.q)
+ choices[answer_num] = pk.encrypt_with_r(plaintexts[plaintext_index], randomness[answer_num])
+
+ # generate proof
+ individual_proofs[answer_num] = choices[answer_num].generate_disjunctive_encryption_proof(plaintexts,
+ plaintext_index,
+ randomness[
+ answer_num],
+ algs.EG_disjunctive_challenge_generator)
+
+ # sum things up homomorphically if needed
+ if max_answers is not None:
+ homomorphic_sum = choices[answer_num] * homomorphic_sum
+ randomness_sum = (randomness_sum + randomness[answer_num]) % pk.q
+
+ # prove that the sum is 0 or 1 (can be "blank vote" for this answer)
+ # num_selected_answers is 0 or 1, which is the index into the plaintext that is actually encoded
+
+ if num_selected_answers < min_answers:
+ raise Exception("Need to select at least %s answer(s)" % min_answers)
+
+ if max_answers is not None:
+ sum_plaintexts = cls.generate_plaintexts(pk, min=min_answers, max=max_answers)
+
+ # need to subtract the min from the offset
+ overall_proof = homomorphic_sum.generate_disjunctive_encryption_proof(sum_plaintexts,
+ num_selected_answers - min_answers,
+ randomness_sum,
+ algs.EG_disjunctive_challenge_generator);
+ else:
+ # approval voting
+ overall_proof = None
+
+ return cls(choices, individual_proofs, overall_proof, randomness, answer_indexes)
- return ea
- @classmethod
- def fromElectionAndAnswer(cls, election, question_num, answer_indexes):
+class EncryptedVote(HeliosObject):
"""
- Given an election, a question number, and a list of answers to that question
- in the form of an array of 0-based indexes into the answer array,
- produce an EncryptedAnswer that works.
+ An encrypted ballot
"""
- question = election.questions[question_num]
- answers = question['answers']
- pk = election.public_key
-
- # initialize choices, individual proofs, randomness and overall proof
- choices = [None for a in range(len(answers))]
- individual_proofs = [None for a in range(len(answers))]
- overall_proof = None
- randomness = [None for a in range(len(answers))]
-
- # possible plaintexts [0, 1]
- plaintexts = cls.generate_plaintexts(pk)
-
- # keep track of number of options selected.
- num_selected_answers = 0;
-
- # homomorphic sum of all
- homomorphic_sum = 0
- randomness_sum = 0
-
- # min and max for number of answers, useful later
- min_answers = 0
- if question.has_key('min'):
- min_answers = question['min']
- max_answers = question['max']
+ FIELDS = ['encrypted_answers', 'election_hash', 'election_uuid']
+
+ def verify(self, election):
+ # correct number of answers
+ # noinspection PyUnresolvedReferences
+ n_answers = len(self.encrypted_answers) if self.encrypted_answers is not None else 0
+ n_questions = len(election.questions) if election.questions is not None else 0
+ if n_answers != n_questions:
+ logging.error(f"Incorrect number of answers ({n_answers}) vs questions ({n_questions})")
+ return False
+
+ # check hash
+ # noinspection PyUnresolvedReferences
+ our_election_hash = self.election_hash if isinstance(self.election_hash, str) else self.election_hash.decode()
+ actual_election_hash = election.hash if isinstance(election.hash, str) else election.hash.decode()
+ if our_election_hash != actual_election_hash:
+ logging.error(f"Incorrect election_hash {our_election_hash} vs {actual_election_hash} ")
+ return False
+
+ # check ID
+ # noinspection PyUnresolvedReferences
+ our_election_uuid = self.election_uuid if isinstance(self.election_uuid, str) else self.election_uuid.decode()
+ actual_election_uuid = election.uuid if isinstance(election.uuid, str) else election.uuid.decode()
+ if our_election_uuid != actual_election_uuid:
+ logging.error(f"Incorrect election_uuid {our_election_uuid} vs {actual_election_uuid} ")
+ return False
+
+ # check proofs on all of answers
+ for question_num in range(len(election.questions)):
+ ea = self.encrypted_answers[question_num]
+
+ question = election.questions[question_num]
+ min_answers = 0
+ if 'min' in question:
+ min_answers = question['min']
+
+ if not ea.verify(election.public_key, min=min_answers, max=question['max']):
+ return False
+
+ return True
+
+ def get_hash(self):
+ return utils.hash_b64(to_json(self.toJSONDict()))
+
+ def toJSONDict(self, with_randomness=False):
+ return {
+ 'answers': [a.toJSONDict(with_randomness) for a in self.encrypted_answers],
+ 'election_hash': self.election_hash,
+ 'election_uuid': self.election_uuid
+ }
+
+ @classmethod
+ def fromJSONDict(cls, d, pk=None):
+ ev = cls()
+
+ ev.encrypted_answers = [EncryptedAnswer.fromJSONDict(ea, pk) for ea in d['answers']]
+ ev.election_hash = d['election_hash']
+ ev.election_uuid = d['election_uuid']
+
+ return ev
+
+ @classmethod
+ def fromElectionAndAnswers(cls, election, answers):
+ pk = election.public_key
+
+ # each answer is an index into the answer array
+ encrypted_answers = [EncryptedAnswer.fromElectionAndAnswer(election, answer_num, answers[answer_num]) for
+ answer_num in range(len(answers))]
+ return cls(encrypted_answers=encrypted_answers, election_hash=election.hash, election_uuid=election.uuid)
- # go through each possible answer and encrypt either a g^0 or a g^1.
- for answer_num in range(len(answers)):
- plaintext_index = 0
- # assuming a list of answers
- if answer_num in answer_indexes:
- plaintext_index = 1
- num_selected_answers += 1
-
- # randomness and encryption
- randomness[answer_num] = algs.Utils.random_mpz_lt(pk.q)
- choices[answer_num] = pk.encrypt_with_r(plaintexts[plaintext_index], randomness[answer_num])
-
- # generate proof
- individual_proofs[answer_num] = choices[answer_num].generate_disjunctive_encryption_proof(plaintexts, plaintext_index,
- randomness[answer_num], algs.EG_disjunctive_challenge_generator)
-
- # sum things up homomorphically if needed
- if max_answers != None:
- homomorphic_sum = choices[answer_num] * homomorphic_sum
- randomness_sum = (randomness_sum + randomness[answer_num]) % pk.q
-
- # prove that the sum is 0 or 1 (can be "blank vote" for this answer)
- # num_selected_answers is 0 or 1, which is the index into the plaintext that is actually encoded
-
- if num_selected_answers < min_answers:
- raise Exception("Need to select at least %s answer(s)" % min_answers)
-
- if max_answers != None:
- sum_plaintexts = cls.generate_plaintexts(pk, min=min_answers, max=max_answers)
+def one_question_winner(question, result, num_cast_votes):
+ """
+ determining the winner for one question
+ """
+ # sort the answers , keep track of the index
+ counts = sorted(enumerate(result), key=lambda x: x[1])
+ counts.reverse()
- # need to subtract the min from the offset
- overall_proof = homomorphic_sum.generate_disjunctive_encryption_proof(sum_plaintexts, num_selected_answers - min_answers, randomness_sum, algs.EG_disjunctive_challenge_generator);
- else:
- # approval voting
- overall_proof = None
+ # if there's a max > 1, we assume that the top MAX win
+ if question['max'] > 1:
+ return [c[0] for c in counts[:question['max']]]
- return cls(choices, individual_proofs, overall_proof, randomness, answer_indexes)
+ # if max = 1, then depends on absolute or relative
+ if question['result_type'] == 'absolute':
+ if counts[0][1] >= (num_cast_votes / 2 + 1):
+ return [counts[0][0]]
+ else:
+ return []
-class EncryptedVote(HeliosObject):
- """
- An encrypted ballot
- """
- FIELDS = ['encrypted_answers', 'election_hash', 'election_uuid']
-
- def verify(self, election):
- # right number of answers
- if len(self.encrypted_answers) != len(election.questions):
- return False
-
- # check hash
- if self.election_hash != election.hash:
- # print "%s / %s " % (self.election_hash, election.hash)
- return False
-
- # check ID
- if self.election_uuid != election.uuid:
- return False
-
- # check proofs on all of answers
- for question_num in range(len(election.questions)):
- ea = self.encrypted_answers[question_num]
-
- question = election.questions[question_num]
- min_answers = 0
- if question.has_key('min'):
- min_answers = question['min']
-
- if not ea.verify(election.public_key, min=min_answers, max=question['max']):
- return False
+ if question['result_type'] == 'relative':
+ return [counts[0][0]]
- return True
-
- def get_hash(self):
- return utils.hash_b64(utils.to_json(self.toJSONDict()))
-
- def toJSONDict(self, with_randomness=False):
- return {
- 'answers': [a.toJSONDict(with_randomness) for a in self.encrypted_answers],
- 'election_hash': self.election_hash,
- 'election_uuid': self.election_uuid
- }
+
+class Election(HeliosObject):
+ FIELDS = ['uuid', 'questions', 'name', 'short_name', 'description', 'voters_hash', 'openreg',
+ 'frozen_at', 'public_key', 'private_key', 'cast_url', 'result', 'result_proof', 'use_voter_aliases',
+ 'voting_starts_at', 'voting_ends_at', 'election_type']
- @classmethod
- def fromJSONDict(cls, d, pk=None):
- ev = cls()
+ JSON_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']
- ev.encrypted_answers = [EncryptedAnswer.fromJSONDict(ea, pk) for ea in d['answers']]
- ev.election_hash = d['election_hash']
- ev.election_uuid = d['election_uuid']
+ # need to add in v3.1: use_advanced_audit_features, election_type, and probably more
- return ev
+ def init_tally(self):
+ return Tally(election=self)
- @classmethod
- def fromElectionAndAnswers(cls, election, answers):
- pk = election.public_key
+ def _process_value_in(self, field_name, field_value):
+ if field_name == 'frozen_at' or field_name == 'voting_starts_at' or field_name == 'voting_ends_at':
+ if isinstance(field_value, str):
+ return datetime.datetime.strptime(field_value, '%Y-%m-%d %H:%M:%S')
- # each answer is an index into the answer array
- encrypted_answers = [EncryptedAnswer.fromElectionAndAnswer(election, answer_num, answers[answer_num]) for answer_num in range(len(answers))]
- return cls(encrypted_answers=encrypted_answers, election_hash=election.hash, election_uuid = election.uuid)
+ if field_name == 'public_key':
+ return algs.EGPublicKey.fromJSONDict(field_value)
+ if field_name == 'private_key':
+ return algs.EGSecretKey.fromJSONDict(field_value)
-def one_question_winner(question, result, num_cast_votes):
- """
- determining the winner for one question
- """
- # sort the answers , keep track of the index
- counts = sorted(enumerate(result), key=lambda(x): x[1])
- counts.reverse()
-
- # if there's a max > 1, we assume that the top MAX win
- if question['max'] > 1:
- return [c[0] for c in counts[:question['max']]]
-
- # if max = 1, then depends on absolute or relative
- if question['result_type'] == 'absolute':
- if counts[0][1] >= (num_cast_votes/2 + 1):
- return [counts[0][0]]
- else:
- return []
-
- if question['result_type'] == 'relative':
- return [counts[0][0]]
+ def _process_value_out(self, field_name, field_value):
+ # the date
+ if field_name == 'frozen_at' or field_name == 'voting_starts_at' or field_name == 'voting_ends_at':
+ return str(field_value)
-class Election(HeliosObject):
+ if field_name == 'public_key' or field_name == 'private_key':
+ return field_value.toJSONDict()
- FIELDS = ['uuid', 'questions', 'name', 'short_name', 'description', 'voters_hash', 'openreg',
- 'frozen_at', 'public_key', 'private_key', 'cast_url', 'result', 'result_proof', 'use_voter_aliases', 'voting_starts_at', 'voting_ends_at', 'election_type']
+ @property
+ def registration_status_pretty(self):
+ if self.openreg:
+ return "Open"
+ else:
+ return "Closed"
- JSON_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']
+ @property
+ def winners(self):
+ """
+ Depending on the type of each question, determine the winners
+ returns an array of winners for each question, aka an array of arrays.
+ assumes that if there is a max to the question, that's how many winners there are.
+ """
+ return [one_question_winner(self.questions[i], self.result[i], self.num_cast_votes) for i in
+ range(len(self.questions))]
- # need to add in v3.1: use_advanced_audit_features, election_type, and probably more
+ @property
+ def pretty_result(self):
+ if not self.result:
+ return None
- def init_tally(self):
- return Tally(election=self)
+ # get the winners
+ winners = self.winners
- def _process_value_in(self, field_name, field_value):
- if field_name == 'frozen_at' or field_name == 'voting_starts_at' or field_name == 'voting_ends_at':
- if type(field_value) == str or type(field_value) == unicode:
- return datetime.datetime.strptime(field_value, '%Y-%m-%d %H:%M:%S')
+ raw_result = self.result
+ prettified_result = []
- if field_name == 'public_key':
- return algs.EGPublicKey.fromJSONDict(field_value)
+ # loop through questions
+ for i in range(len(self.questions)):
+ q = self.questions[i]
+ pretty_question = []
- if field_name == 'private_key':
- return algs.EGSecretKey.fromJSONDict(field_value)
+ # go through answers
+ for j in range(len(q['answers'])):
+ a = q['answers'][j]
+ count = raw_result[i][j]
+ pretty_question.append({'answer': a, 'count': count, 'winner': (j in winners[i])})
- def _process_value_out(self, field_name, field_value):
- # the date
- if field_name == 'frozen_at' or field_name == 'voting_starts_at' or field_name == 'voting_ends_at':
- return str(field_value)
+ prettified_result.append({'question': q['short_name'], 'answers': pretty_question})
- if field_name == 'public_key' or field_name == 'private_key':
- return field_value.toJSONDict()
+ return prettified_result
- @property
- def registration_status_pretty(self):
- if self.openreg:
- return "Open"
- else:
- return "Closed"
- @property
- def winners(self):
+class Voter(HeliosObject):
"""
- Depending on the type of each question, determine the winners
- returns an array of winners for each question, aka an array of arrays.
- assumes that if there is a max to the question, that's how many winners there are.
+ A voter in an election
"""
- return [one_question_winner(self.questions[i], self.result[i], self.num_cast_votes) for i in range(len(self.questions))]
+ FIELDS = ['election_uuid', 'uuid', 'voter_type', 'voter_id', 'name', 'alias']
+ JSON_FIELDS = ['election_uuid', 'uuid', 'voter_type', 'voter_id_hash', 'name']
- @property
- def pretty_result(self):
- if not self.result:
- return None
+ # alternative, for when the voter is aliased
+ ALIASED_VOTER_JSON_FIELDS = ['election_uuid', 'uuid', 'alias']
- # get the winners
- winners = self.winners
+ def toJSONDict(self):
+ if self.alias is not None:
+ return super(Voter, self).toJSONDict(self.ALIASED_VOTER_JSON_FIELDS)
+ else:
+ return super(Voter, self).toJSONDict()
- raw_result = self.result
- prettified_result = []
+ @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)
- # loop through questions
- for i in range(len(self.questions)):
- q = self.questions[i]
- pretty_question = []
-
- # go through answers
- for j in range(len(q['answers'])):
- a = q['answers'][j]
- count = raw_result[i][j]
- pretty_question.append({'answer': a, 'count': count, 'winner': (j in winners[i])})
-
- prettified_result.append({'question': q['short_name'], 'answers': pretty_question})
-
- return prettified_result
-
-
-class Voter(HeliosObject):
- """
- A voter in an election
- """
- FIELDS = ['election_uuid', 'uuid', 'voter_type', 'voter_id', 'name', 'alias']
- JSON_FIELDS = ['election_uuid', 'uuid', 'voter_type', 'voter_id_hash', 'name']
-
- # alternative, for when the voter is aliased
- ALIASED_VOTER_JSON_FIELDS = ['election_uuid', 'uuid', 'alias']
-
- def toJSONDict(self):
- fields = None
- if self.alias != None:
- return super(Voter, self).toJSONDict(self.ALIASED_VOTER_JSON_FIELDS)
- else:
- return super(Voter,self).toJSONDict()
-
- @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)
class Trustee(HeliosObject):
- """
- a trustee
- """
- FIELDS = ['uuid', 'public_key', 'public_key_hash', 'pok', 'decryption_factors', 'decryption_proofs', 'email']
-
- def _process_value_in(self, field_name, field_value):
- if field_name == 'public_key':
- return algs.EGPublicKey.fromJSONDict(field_value)
-
- if field_name == 'pok':
- return algs.DLogProof.fromJSONDict(field_value)
-
- def _process_value_out(self, field_name, field_value):
- if field_name == 'public_key' or field_name == 'pok':
- return field_value.toJSONDict()
-
-class CastVote(HeliosObject):
- """
- A cast vote, which includes an encrypted vote and some cast metadata
- """
- FIELDS = ['vote', 'cast_at', 'voter_uuid', 'voter_hash', 'vote_hash']
-
- def __init__(self, *args, **kwargs):
- super(CastVote, self).__init__(*args, **kwargs)
- self.election = None
-
- @classmethod
- def fromJSONDict(cls, d, election=None):
- o = cls()
- o.election = election
- o.set_from_args(**d)
- return o
-
- def toJSONDict(self, include_vote=True):
- result = super(CastVote,self).toJSONDict()
- if not include_vote:
- del result['vote']
- return result
-
- @classmethod
- def fromOtherObject(cls, o, election):
- obj = cls()
- obj.election = election
- obj.set_from_other_object(o)
- return obj
-
- def _process_value_in(self, field_name, field_value):
- if field_name == 'cast_at':
- if type(field_value) == str:
- return datetime.datetime.strptime(field_value, '%Y-%m-%d %H:%M:%S')
-
- if field_name == 'vote':
- return EncryptedVote.fromJSONDict(field_value, self.election.public_key)
-
- def _process_value_out(self, field_name, field_value):
- # the date
- if field_name == 'cast_at':
- return str(field_value)
-
- if field_name == 'vote':
- return field_value.toJSONDict()
-
- def issues(self, election):
"""
- Look for consistency problems
+ a trustee
"""
- issues = []
-
- # check the election
- if self.vote.election_uuid != election.uuid:
- issues.append("the vote's election UUID does not match the election for which this vote is being cast")
-
- return issues
-
-class DLogTable(object):
- """
- Keeping track of discrete logs
- """
-
- def __init__(self, base, modulus):
- self.dlogs = {}
- self.dlogs[1] = 0
- self.last_dlog_result = 1
- self.counter = 0
-
- self.base = base
- self.modulus = modulus
+ FIELDS = ['uuid', 'public_key', 'public_key_hash', 'pok', 'decryption_factors', 'decryption_proofs', 'email']
- def increment(self):
- self.counter += 1
+ def _process_value_in(self, field_name, field_value):
+ if field_name == 'public_key':
+ return algs.EGPublicKey.fromJSONDict(field_value)
- # new value
- new_value = (self.last_dlog_result * self.base) % self.modulus
+ if field_name == 'pok':
+ return algs.DLogProof.fromJSONDict(field_value)
- # record the discrete log
- self.dlogs[new_value] = self.counter
+ def _process_value_out(self, field_name, field_value):
+ if field_name == 'public_key' or field_name == 'pok':
+ return field_value.toJSONDict()
- # record the last value
- self.last_dlog_result = new_value
-
- def precompute(self, up_to):
- while self.counter < up_to:
- self.increment()
-
- def lookup(self, value):
- return self.dlogs.get(value, None)
+class CastVote(HeliosObject):
+ """
+ A cast vote, which includes an encrypted vote and some cast metadata
+ """
+ FIELDS = ['vote', 'cast_at', 'voter_uuid', 'voter_hash', 'vote_hash']
-class Tally(HeliosObject):
- """
- A running homomorphic tally
- """
+ def __init__(self, *args, **kwargs):
+ super(CastVote, self).__init__(*args, **kwargs)
+ self.election = None
- FIELDS = ['num_tallied', 'tally']
- JSON_FIELDS = ['num_tallied', 'tally']
+ @classmethod
+ def fromJSONDict(cls, d, election=None):
+ o = cls()
+ o.election = election
+ o.set_from_args(**d)
+ return o
- def __init__(self, *args, **kwargs):
- super(Tally, self).__init__(*args, **kwargs)
+ def toJSONDict(self, include_vote=True):
+ result = super(CastVote, self).toJSONDict()
+ if not include_vote:
+ del result['vote']
+ return result
- self.election = kwargs.get('election',None)
+ @classmethod
+ def fromOtherObject(cls, o, election):
+ obj = cls()
+ obj.election = election
+ obj.set_from_other_object(o)
+ return obj
- if self.election:
- self.init_election(self.election)
- else:
- self.questions = None
- self.public_key = None
+ def _process_value_in(self, field_name, field_value):
+ if field_name == 'cast_at':
+ if isinstance(field_value, str):
+ return datetime.datetime.strptime(field_value, '%Y-%m-%d %H:%M:%S')
- if not self.tally:
- self.tally = None
+ if field_name == 'vote':
+ return EncryptedVote.fromJSONDict(field_value, self.election.public_key)
- # initialize
- if self.num_tallied == None:
- self.num_tallied = 0
+ def _process_value_out(self, field_name, field_value):
+ # the date
+ if field_name == 'cast_at':
+ return str(field_value)
- def init_election(self, election):
- """
- given the election, initialize some params
- """
- self.questions = election.questions
- self.public_key = election.public_key
+ if field_name == 'vote':
+ return field_value.toJSONDict()
- if not self.tally:
- self.tally = [[0 for a in q['answers']] for q in self.questions]
+ def issues(self, election):
+ """
+ Look for consistency problems
+ """
+ issues = []
- def add_vote_batch(self, encrypted_votes, verify_p=True):
- """
- Add a batch of votes. Eventually, this will be optimized to do an aggregate proof verification
- rather than a whole proof verif for each vote.
- """
- for vote in encrypted_votes:
- self.add_vote(vote, verify_p)
-
- def add_vote(self, encrypted_vote, verify_p=True):
- # do we verify?
- if verify_p:
- if not encrypted_vote.verify(self.election):
- raise Exception('Bad Vote')
-
- # for each question
- for question_num in range(len(self.questions)):
- question = self.questions[question_num]
- answers = question['answers']
-
- # for each possible answer to each question
- for answer_num in range(len(answers)):
- # do the homomorphic addition into the tally
- enc_vote_choice = encrypted_vote.encrypted_answers[question_num].choices[answer_num]
- enc_vote_choice.pk = self.public_key
- self.tally[question_num][answer_num] = encrypted_vote.encrypted_answers[question_num].choices[answer_num] * self.tally[question_num][answer_num]
-
- self.num_tallied += 1
-
- def decryption_factors_and_proofs(self, sk):
- """
- returns an array of decryption factors and a corresponding array of decryption proofs.
- makes the decryption factors into strings, for general Helios / JS compatibility.
- """
- # for all choices of all questions (double list comprehension)
- decryption_factors = []
- decryption_proof = []
+ # check the election
+ if self.vote.election_uuid != election.uuid:
+ issues.append("the vote's election UUID does not match the election for which this vote is being cast")
- for question_num, question in enumerate(self.questions):
- answers = question['answers']
- question_factors = []
- question_proof = []
+ return issues
- for answer_num, answer in enumerate(answers):
- # do decryption and proof of it
- dec_factor, proof = sk.decryption_factor_and_proof(self.tally[question_num][answer_num])
- # look up appropriate discrete log
- # this is the string conversion
- question_factors.append(str(dec_factor))
- question_proof.append(proof.toJSONDict())
-
- decryption_factors.append(question_factors)
- decryption_proof.append(question_proof)
-
- return decryption_factors, decryption_proof
-
- def decrypt_and_prove(self, sk, discrete_logs=None):
+class DLogTable(object):
"""
- returns an array of tallies and a corresponding array of decryption proofs.
+ Keeping track of discrete logs
"""
- # who's keeping track of discrete logs?
- if not discrete_logs:
- discrete_logs = self.discrete_logs
+ def __init__(self, base, modulus):
+ self.dlogs = {1: 0}
+ self.last_dlog_result = 1
+ self.counter = 0
- # for all choices of all questions (double list comprehension)
- decrypted_tally = []
- decryption_proof = []
+ self.base = base
+ self.modulus = modulus
- for question_num in range(len(self.questions)):
- question = self.questions[question_num]
- answers = question['answers']
- question_tally = []
- question_proof = []
+ def increment(self):
+ self.counter += 1
- for answer_num in range(len(answers)):
- # do decryption and proof of it
- plaintext, proof = sk.prove_decryption(self.tally[question_num][answer_num])
+ # new value
+ new_value = (self.last_dlog_result * self.base) % self.modulus
- # look up appropriate discrete log
- question_tally.append(discrete_logs[plaintext])
- question_proof.append(proof)
+ # record the discrete log
+ self.dlogs[new_value] = self.counter
- decrypted_tally.append(question_tally)
- decryption_proof.append(question_proof)
+ # record the last value
+ self.last_dlog_result = new_value
- return decrypted_tally, decryption_proof
+ def precompute(self, up_to):
+ while self.counter < up_to:
+ self.increment()
- def verify_decryption_proofs(self, decryption_factors, decryption_proofs, public_key, challenge_generator):
- """
- decryption_factors is a list of lists of dec factors
- decryption_proofs are the corresponding proofs
- public_key is, of course, the public key of the trustee
- """
+ def lookup(self, value):
+ return self.dlogs.get(value, None)
- # go through each one
- for q_num, q in enumerate(self.tally):
- for a_num, answer_tally in enumerate(q):
- # parse the proof
- proof = algs.EGZKProof.fromJSONDict(decryption_proofs[q_num][a_num])
- # check that g, alpha, y, dec_factor is a DH tuple
- if not proof.verify(public_key.g, answer_tally.alpha, public_key.y, int(decryption_factors[q_num][a_num]), public_key.p, public_key.q, challenge_generator):
- return False
-
- return True
-
- def decrypt_from_factors(self, decryption_factors, public_key):
+class Tally(HeliosObject):
"""
- decrypt a tally given decryption factors
-
- The decryption factors are a list of decryption factor sets, for each trustee.
- Each decryption factor set is a list of lists of decryption factors (questions/answers).
+ A running homomorphic tally
"""
- # pre-compute a dlog table
- dlog_table = DLogTable(base = public_key.g, modulus = public_key.p)
- dlog_table.precompute(self.num_tallied)
-
- result = []
+ FIELDS = ['num_tallied', 'tally']
+ JSON_FIELDS = ['num_tallied', 'tally']
+
+ def __init__(self, *args, **kwargs):
+ super(Tally, self).__init__(*args, **kwargs)
+
+ self.election = kwargs.get('election', None)
+
+ if self.election:
+ self.init_election(self.election)
+ else:
+ self.questions = None
+ self.public_key = None
+
+ if not self.tally:
+ self.tally = None
+
+ # initialize
+ if self.num_tallied is None:
+ self.num_tallied = 0
+
+ def init_election(self, election):
+ """
+ given the election, initialize some params
+ """
+ self.questions = election.questions
+ self.public_key = election.public_key
+
+ if not self.tally:
+ self.tally = [[0 for _ in q['answers']] for q in self.questions]
+
+ def add_vote_batch(self, encrypted_votes, verify_p=True):
+ """
+ Add a batch of votes. Eventually, this will be optimized to do an aggregate proof verification
+ rather than a whole proof verif for each vote.
+ """
+ for vote in encrypted_votes:
+ self.add_vote(vote, verify_p)
+
+ def add_vote(self, encrypted_vote, verify_p=True):
+ # do we verify?
+ if verify_p:
+ if not encrypted_vote.verify(self.election):
+ raise Exception('Bad Vote')
+
+ # for each question
+ for question_num in range(len(self.questions)):
+ question = self.questions[question_num]
+ answers = question['answers']
+
+ # for each possible answer to each question
+ for answer_num in range(len(answers)):
+ # do the homomorphic addition into the tally
+ enc_vote_choice = encrypted_vote.encrypted_answers[question_num].choices[answer_num]
+ enc_vote_choice.pk = self.public_key
+ self.tally[question_num][answer_num] = encrypted_vote.encrypted_answers[question_num].choices[
+ answer_num] * self.tally[question_num][answer_num]
+
+ self.num_tallied += 1
+
+ def decryption_factors_and_proofs(self, sk):
+ """
+ returns an array of decryption factors and a corresponding array of decryption proofs.
+ makes the decryption factors into strings, for general Helios / JS compatibility.
+ """
+ # for all choices of all questions (double list comprehension)
+ decryption_factors = []
+ decryption_proof = []
+
+ for question_num, question in enumerate(self.questions):
+ answers = question['answers']
+ question_factors = []
+ question_proof = []
+
+ for answer_num, answer in enumerate(answers):
+ # do decryption and proof of it
+ dec_factor, proof = sk.decryption_factor_and_proof(self.tally[question_num][answer_num])
+
+ # look up appropriate discrete log
+ # this is the string conversion
+ question_factors.append(str(dec_factor))
+ question_proof.append(proof.toJSONDict())
+
+ decryption_factors.append(question_factors)
+ decryption_proof.append(question_proof)
+
+ return decryption_factors, decryption_proof
+
+ def decrypt_and_prove(self, sk, discrete_logs=None):
+ """
+ returns an array of tallies and a corresponding array of decryption proofs.
+ """
+
+ # who's keeping track of discrete logs?
+ if not discrete_logs:
+ discrete_logs = self.discrete_logs
+
+ # for all choices of all questions (double list comprehension)
+ decrypted_tally = []
+ decryption_proof = []
+
+ for question_num in range(len(self.questions)):
+ question = self.questions[question_num]
+ answers = question['answers']
+ question_tally = []
+ question_proof = []
+
+ for answer_num in range(len(answers)):
+ # do decryption and proof of it
+ plaintext, proof = sk.prove_decryption(self.tally[question_num][answer_num])
+
+ # look up appropriate discrete log
+ question_tally.append(discrete_logs[plaintext])
+ question_proof.append(proof)
+
+ decrypted_tally.append(question_tally)
+ decryption_proof.append(question_proof)
+
+ return decrypted_tally, decryption_proof
+
+ def verify_decryption_proofs(self, decryption_factors, decryption_proofs, public_key, challenge_generator):
+ """
+ decryption_factors is a list of lists of dec factors
+ decryption_proofs are the corresponding proofs
+ public_key is, of course, the public key of the trustee
+ """
+
+ # go through each one
+ for q_num, q in enumerate(self.tally):
+ for a_num, answer_tally in enumerate(q):
+ # parse the proof
+ proof = algs.EGZKProof.fromJSONDict(decryption_proofs[q_num][a_num])
+
+ # check that g, alpha, y, dec_factor is a DH tuple
+ if not proof.verify(public_key.g, answer_tally.alpha, public_key.y,
+ int(decryption_factors[q_num][a_num]), public_key.p, public_key.q,
+ challenge_generator):
+ return False
+
+ return True
+
+ def decrypt_from_factors(self, decryption_factors, public_key):
+ """
+ decrypt a tally given decryption factors
+
+ The decryption factors are a list of decryption factor sets, for each trustee.
+ Each decryption factor set is a list of lists of decryption factors (questions/answers).
+ """
+
+ # pre-compute a dlog table
+ dlog_table = DLogTable(base=public_key.g, modulus=public_key.p)
+ dlog_table.precompute(self.num_tallied)
+
+ result = []
- # go through each one
- for q_num, q in enumerate(self.tally):
- q_result = []
+ # go through each one
+ for q_num, q in enumerate(self.tally):
+ q_result = []
- for a_num, a in enumerate(q):
- # coalesce the decryption factors into one list
- dec_factor_list = [df[q_num][a_num] for df in decryption_factors]
- raw_value = self.tally[q_num][a_num].decrypt(dec_factor_list, public_key)
+ for a_num, a in enumerate(q):
+ # coalesce the decryption factors into one list
+ dec_factor_list = [df[q_num][a_num] for df in decryption_factors]
+ raw_value = self.tally[q_num][a_num].decrypt(dec_factor_list, public_key)
- q_result.append(dlog_table.lookup(raw_value))
+ q_result.append(dlog_table.lookup(raw_value))
- result.append(q_result)
+ result.append(q_result)
- return result
+ return result
- def _process_value_in(self, field_name, field_value):
- if field_name == 'tally':
- return [[algs.EGCiphertext.fromJSONDict(a) for a in q] for q in field_value]
+ def _process_value_in(self, field_name, field_value):
+ if field_name == 'tally':
+ return [[algs.EGCiphertext.fromJSONDict(a) for a in q] for q in field_value]
- def _process_value_out(self, field_name, field_value):
- if field_name == 'tally':
- return [[a.toJSONDict() for a in q] for q in field_value]
+ def _process_value_out(self, field_name, field_value):
+ if field_name == 'tally':
+ return [[a.toJSONDict() for a in q] for q in field_value]
diff --git a/helios/crypto/elgamal.py b/helios/crypto/elgamal.py
index 88a08c01b..33eb03083 100644
--- a/helios/crypto/elgamal.py
+++ b/helios/crypto/elgamal.py
@@ -8,12 +8,13 @@
ben@adida.net
"""
-import math, hashlib, logging
-import randpool, number
+import logging
-import numtheory
+from Crypto.Hash import SHA1
+from Crypto.Util.number import inverse
+
+from helios.crypto.utils import random
-from algs import Utils
class Cryptosystem(object):
def __init__(self):
@@ -21,30 +22,6 @@ def __init__(self):
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
@@ -68,7 +45,7 @@ def generate(self, p, q, g):
self.pk.p = p
self.pk.q = q
- self.sk.x = Utils.random_mpz_lt(q)
+ self.sk.x = random.mpz_lt(q)
self.pk.y = pow(g, self.sk.x, p)
self.sk.public_key = self.pk
@@ -106,7 +83,7 @@ def encrypt_return_r(self, plaintext):
"""
Encrypt a plaintext and return the randomness just generated and used.
"""
- r = Utils.random_mpz_lt(self.q)
+ r = random.mpz_lt(self.q)
ciphertext = self.encrypt_with_r(plaintext, r)
return [ciphertext, r]
@@ -181,7 +158,7 @@ def decrypt(self, ciphertext, dec_factor = None, decode_m=False):
if not dec_factor:
dec_factor = self.decryption_factor(ciphertext)
- m = (Utils.inverse(dec_factor, self.pk.p) * ciphertext.beta) % self.pk.p
+ m = (inverse(dec_factor, self.pk.p) * ciphertext.beta) % self.pk.p
if decode_m:
# get m back from the q-order subgroup
@@ -207,15 +184,15 @@ def prove_decryption(self, ciphertext):
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
+ m = (inverse(pow(ciphertext.alpha, self.x, self.pk.p), self.pk.p) * ciphertext.beta) % self.pk.p
+ beta_over_m = (ciphertext.beta * inverse(m, self.pk.p)) % self.pk.p
# pick a random w
- w = Utils.random_mpz_lt(self.pk.q)
+ w = 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)
+ c = int(SHA1.new(bytes(str(a) + "," + str(b), 'utf-8')).hexdigest(),16)
t = (w + self.x * c) % self.pk.q
@@ -232,7 +209,7 @@ def prove_sk(self, challenge_generator):
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)
+ w = 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
@@ -255,7 +232,7 @@ def __mul__(self,other):
"""
Homomorphic Multiplication of ciphertexts.
"""
- if type(other) == int and (other == 0 or other == 1):
+ if isinstance(other, int) and (other == 0 or other == 1):
return self
if self.pk != other.pk:
@@ -287,7 +264,7 @@ def reenc_return_r(self):
"""
Reencryption with fresh randomness, which is returned.
"""
- r = Utils.random_mpz_lt(self.pk.q)
+ r = random.mpz_lt(self.pk.q)
new_c = self.reenc_with_r(r)
return [new_c, r]
@@ -301,17 +278,17 @@ def __eq__(self, other):
"""
Check for ciphertext equality.
"""
- if other == None:
+ if other is None:
return False
- return (self.alpha == other.alpha and self.beta == other.beta)
+ 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)
+ w = random.mpz_lt(self.pk.q)
# build the proof
proof = ZKProof()
@@ -331,20 +308,20 @@ def generate_encryption_proof(self, plaintext, randomness, challenge_generator):
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)
+ challenge = 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
+ beta_over_plaintext = (self.beta * 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);
+ proof.response = 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
+ proof.commitment['A'] = (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'] = (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
@@ -397,7 +374,7 @@ def verify_encryption_proof(self, plaintext, proof):
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
+ beta_over_m = (self.beta * 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)
@@ -416,7 +393,7 @@ def verify_disjunctive_encryption_proof(self, plaintexts, proof, challenge_gener
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])
+ print("bad proof %s, %s, %s" % (i, plaintexts[i], proof.proofs[i]))
return False
# logging.info("made it past the two encryption proofs")
@@ -444,7 +421,7 @@ def decrypt(self, decryption_factors, public_key):
"""
running_decryption = self.beta
for dec_factor in decryption_factors:
- running_decryption = (running_decryption * Utils.inverse(dec_factor, public_key.p)) % public_key.p
+ running_decryption = (running_decryption * inverse(dec_factor, public_key.p)) % public_key.p
return running_decryption
@@ -473,7 +450,7 @@ def generate(cls, little_g, little_h, x, p, q, challenge_generator):
"""
# generate random w
- w = Utils.random_mpz_lt(q)
+ w = random.mpz_lt(q)
# create proof instance
proof = cls()
@@ -526,7 +503,7 @@ def disjunctive_challenge_generator(commitments):
array_to_hash.append(str(commitment['B']))
string_to_hash = ",".join(array_to_hash)
- return int(hashlib.sha1(string_to_hash).hexdigest(),16)
+ return int(SHA1.new(bytes(string_to_hash, 'utf-8')).hexdigest(),16)
# a challenge generator for Fiat-Shamir with A,B commitment
def fiatshamir_challenge_generator(commitment):
@@ -534,5 +511,5 @@ def fiatshamir_challenge_generator(commitment):
def DLog_challenge_generator(commitment):
string_to_hash = str(commitment)
- return int(hashlib.sha1(string_to_hash).hexdigest(),16)
+ return int(SHA1.new(bytes(string_to_hash, 'utf-8')).hexdigest(),16)
diff --git a/helios/crypto/number.py b/helios/crypto/number.py
deleted file mode 100644
index 9d50563e9..000000000
--- a/helios/crypto/number.py
+++ /dev/null
@@ -1,201 +0,0 @@
-#
-# number.py : Number-theoretic functions
-#
-# Part of the Python Cryptography Toolkit
-#
-# Distribute and use freely; there are no restrictions on further
-# dissemination and usage except those imposed by the laws of your
-# country of residence. This software is provided "as is" without
-# warranty of fitness for use or suitability for any purpose, express
-# or implied. Use at your own risk or not at all.
-#
-
-__revision__ = "$Id: number.py,v 1.13 2003/04/04 18:21:07 akuchling Exp $"
-
-bignum = long
-try:
- from Crypto.PublicKey import _fastmath
-except ImportError:
- _fastmath = None
-
-# Commented out and replaced with faster versions below
-## def long2str(n):
-## s=''
-## while n>0:
-## s=chr(n & 255)+s
-## n=n>>8
-## return s
-
-## import types
-## def str2long(s):
-## if type(s)!=types.StringType: return s # Integers will be left alone
-## return reduce(lambda x,y : x*256+ord(y), s, 0L)
-
-def size (N):
- """size(N:long) : int
- Returns the size of the number N in bits.
- """
- bits, power = 0,1L
- while N >= power:
- bits += 1
- power = power << 1
- return bits
-
-def getRandomNumber(N, randfunc):
- """getRandomNumber(N:int, randfunc:callable):long
- Return an N-bit random number."""
-
- S = randfunc(N/8)
- odd_bits = N % 8
- if odd_bits != 0:
- char = ord(randfunc(1)) >> (8-odd_bits)
- S = chr(char) + S
- value = bytes_to_long(S)
- value |= 2L ** (N-1) # Ensure high bit is set
- assert size(value) >= N
- return value
-
-def GCD(x,y):
- """GCD(x:long, y:long): long
- Return the GCD of x and y.
- """
- x = abs(x) ; y = abs(y)
- while x > 0:
- x, y = y % x, x
- return y
-
-def inverse(u, v):
- """inverse(u:long, u:long):long
- Return the inverse of u mod v.
- """
- u3, v3 = long(u), long(v)
- u1, v1 = 1L, 0L
- while v3 > 0:
- q=u3 / v3
- u1, v1 = v1, u1 - v1*q
- u3, v3 = v3, u3 - v3*q
- while u1<0:
- u1 = u1 + v
- return u1
-
-# Given a number of bits to generate and a random generation function,
-# find a prime number of the appropriate size.
-
-def getPrime(N, randfunc):
- """getPrime(N:int, randfunc:callable):long
- Return a random N-bit prime number.
- """
-
- number=getRandomNumber(N, randfunc) | 1
- while (not isPrime(number)):
- number=number+2
- return number
-
-def isPrime(N):
- """isPrime(N:long):bool
- Return true if N is prime.
- """
- if N == 1:
- return 0
- if N in sieve:
- return 1
- for i in sieve:
- if (N % i)==0:
- return 0
-
- # Use the accelerator if available
- if _fastmath is not None:
- return _fastmath.isPrime(N)
-
- # Compute the highest bit that's set in N
- N1 = N - 1L
- n = 1L
- while (n> 1L
-
- # Rabin-Miller test
- for c in sieve[:7]:
- a=long(c) ; d=1L ; t=n
- while (t): # Iterate over the bits in N1
- x=(d*d) % N
- if x==1L and d!=1L and d!=N1:
- return 0 # Square root of 1 found
- if N1 & t:
- d=(x*a) % N
- else:
- d=x
- t = t >> 1L
- if d!=1L:
- return 0
- return 1
-
-# Small primes used for checking primality; these are all the primes
-# less than 256. This should be enough to eliminate most of the odd
-# numbers before needing to do a Rabin-Miller test at all.
-
-sieve=[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59,
- 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127,
- 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193,
- 197, 199, 211, 223, 227, 229, 233, 239, 241, 251]
-
-# Improved conversion functions contributed by Barry Warsaw, after
-# careful benchmarking
-
-import struct
-
-def long_to_bytes(n, blocksize=0):
- """long_to_bytes(n:long, blocksize:int) : string
- Convert a long integer to a byte string.
-
- If optional blocksize is given and greater than zero, pad the front of the
- byte string with binary zeros so that the length is a multiple of
- blocksize.
- """
- # after much testing, this algorithm was deemed to be the fastest
- s = ''
- n = long(n)
- pack = struct.pack
- while n > 0:
- s = pack('>I', n & 0xffffffffL) + s
- n = n >> 32
- # strip off leading zeros
- for i in range(len(s)):
- if s[i] != '\000':
- break
- else:
- # only happens when n == 0
- s = '\000'
- i = 0
- s = s[i:]
- # add back some pad bytes. this could be done more efficiently w.r.t. the
- # de-padding being done above, but sigh...
- if blocksize > 0 and len(s) % blocksize:
- s = (blocksize - len(s) % blocksize) * '\000' + s
- return s
-
-def bytes_to_long(s):
- """bytes_to_long(string) : long
- Convert a byte string to a long integer.
-
- This is (essentially) the inverse of long_to_bytes().
- """
- acc = 0L
- unpack = struct.unpack
- length = len(s)
- if length % 4:
- extra = (4 - length % 4)
- s = '\000' * extra + s
- length = length + extra
- for i in range(0, length, 4):
- acc = (acc << 32) + unpack('>I', s[i:i+4])[0]
- return acc
-
-# For backwards compatibility...
-import warnings
-def long2str(n, blocksize=0):
- warnings.warn("long2str() has been replaced by long_to_bytes()")
- return long_to_bytes(n, blocksize)
-def str2long(s):
- warnings.warn("str2long() has been replaced by bytes_to_long()")
- return bytes_to_long(s)
diff --git a/helios/crypto/numtheory.py b/helios/crypto/numtheory.py
index 16fcf9aa0..e16691745 100644
--- a/helios/crypto/numtheory.py
+++ b/helios/crypto/numtheory.py
@@ -103,7 +103,7 @@ def trial_division(n, bound=None):
if n == 1: return 1
for p in [2, 3, 5]:
if n%p == 0: return p
- if bound == None: bound = n
+ if bound is None: bound = n
dif = [6, 4, 2, 4, 2, 4, 6, 2]
m = 7; i = 1
while m <= bound and m*m <= n:
@@ -207,7 +207,7 @@ def inversemod(a, n):
"""
g, x, y = xgcd(a, n)
if g != 1:
- raise ZeroDivisionError, (a,n)
+ raise ZeroDivisionError(a,n)
assert g == 1, "a must be coprime to n."
return x%n
@@ -225,7 +225,7 @@ def solve_linear(a,b,n):
Examples:
>>> solve_linear(4, 2, 10)
8
- >>> solve_linear(2, 1, 4) == None
+ >>> solve_linear(2, 1, 4) is None
True
"""
g, c, _ = xgcd(a,n) # (1)
@@ -1014,7 +1014,7 @@ def elliptic_curve_method(N, m, tries=5):
E, P = randcurve(N) # (2)
try: # (3)
Q = ellcurve_mul(E, m, P) # (4)
- except ZeroDivisionError, x: # (5)
+ except ZeroDivisionError as x: # (5)
g = gcd(x[0],N) # (6)
if g != 1 or g != N: return g # (7)
return N
@@ -1153,7 +1153,7 @@ def __mul__(self, other):
return r
def __neg__(self):
v = {}
- for m in self.v.keys():
+ for m in list(self.v.keys()):
v[m] = -self.v[m]
return Poly(v)
def __div__(self, other):
@@ -1161,7 +1161,7 @@ def __div__(self, other):
def __getitem__(self, m): # (6)
m = tuple(m)
- if not self.v.has_key(m): self.v[m] = 0
+ if m not in self.v: self.v[m] = 0
return self.v[m]
def __setitem__(self, m, c):
self.v[tuple(m)] = c
@@ -1169,7 +1169,7 @@ def __delitem__(self, m):
del self.v[tuple(m)]
def monomials(self): # (7)
- return self.v.keys()
+ return list(self.v.keys())
def normalize(self): # (8)
while True:
finished = True
@@ -1244,8 +1244,8 @@ def prove_associative(): # (15)
- (x3 + x4)*(x3 - x4)*(x3 - x4))
s2 = (x3 - x4)*(x3 - x4)*((y1 - y5)*(y1 - y5) \
- (x1 + x5)*(x1 - x5)*(x1 - x5))
- print "Associative?"
- print s1 == s2 # (17)
+ print("Associative?")
+ print(s1 == s2) # (17)
diff --git a/helios/crypto/randpool.py b/helios/crypto/randpool.py
deleted file mode 100644
index 53a8acc03..000000000
--- a/helios/crypto/randpool.py
+++ /dev/null
@@ -1,422 +0,0 @@
-#
-# randpool.py : Cryptographically strong random number generation
-#
-# Part of the Python Cryptography Toolkit
-#
-# Distribute and use freely; there are no restrictions on further
-# dissemination and usage except those imposed by the laws of your
-# country of residence. This software is provided "as is" without
-# warranty of fitness for use or suitability for any purpose, express
-# or implied. Use at your own risk or not at all.
-#
-
-__revision__ = "$Id: randpool.py,v 1.14 2004/05/06 12:56:54 akuchling Exp $"
-
-import time, array, types, warnings, os.path
-from number import long_to_bytes
-try:
- import Crypto.Util.winrandom as winrandom
-except:
- winrandom = None
-
-STIRNUM = 3
-
-class RandomPool:
- """randpool.py : Cryptographically strong random number generation.
-
- The implementation here is similar to the one in PGP. To be
- cryptographically strong, it must be difficult to determine the RNG's
- output, whether in the future or the past. This is done by using
- a cryptographic hash function to "stir" the random data.
-
- Entropy is gathered in the same fashion as PGP; the highest-resolution
- clock around is read and the data is added to the random number pool.
- A conservative estimate of the entropy is then kept.
-
- If a cryptographically secure random source is available (/dev/urandom
- on many Unixes, Windows CryptGenRandom on most Windows), then use
- it.
-
- Instance Attributes:
- bits : int
- Maximum size of pool in bits
- bytes : int
- Maximum size of pool in bytes
- entropy : int
- Number of bits of entropy in this pool.
-
- Methods:
- add_event([s]) : add some entropy to the pool
- get_bytes(int) : get N bytes of random data
- randomize([N]) : get N bytes of randomness from external source
- """
-
-
- def __init__(self, numbytes = 160, cipher=None, hash=None):
- if hash is None:
- from hashlib import sha1 as hash
-
- # The cipher argument is vestigial; it was removed from
- # version 1.1 so RandomPool would work even in the limited
- # exportable subset of the code
- if cipher is not None:
- warnings.warn("'cipher' parameter is no longer used")
-
- if isinstance(hash, types.StringType):
- # ugly hack to force __import__ to give us the end-path module
- hash = __import__('Crypto.Hash.'+hash,
- None, None, ['new'])
- warnings.warn("'hash' parameter should now be a hashing module")
-
- self.bytes = numbytes
- self.bits = self.bytes*8
- self.entropy = 0
- self._hash = hash
-
- # Construct an array to hold the random pool,
- # initializing it to 0.
- self._randpool = array.array('B', [0]*self.bytes)
-
- self._event1 = self._event2 = 0
- self._addPos = 0
- self._getPos = hash().digest_size
- self._lastcounter=time.time()
- self.__counter = 0
-
- self._measureTickSize() # Estimate timer resolution
- self._randomize()
-
- def _updateEntropyEstimate(self, nbits):
- self.entropy += nbits
- if self.entropy < 0:
- self.entropy = 0
- elif self.entropy > self.bits:
- self.entropy = self.bits
-
- def _randomize(self, N = 0, devname = '/dev/urandom'):
- """_randomize(N, DEVNAME:device-filepath)
- collects N bits of randomness from some entropy source (e.g.,
- /dev/urandom on Unixes that have it, Windows CryptoAPI
- CryptGenRandom, etc)
- DEVNAME is optional, defaults to /dev/urandom. You can change it
- to /dev/random if you want to block till you get enough
- entropy.
- """
- data = ''
- if N <= 0:
- nbytes = int((self.bits - self.entropy)/8+0.5)
- else:
- nbytes = int(N/8+0.5)
- if winrandom:
- # Windows CryptGenRandom provides random data.
- data = winrandom.new().get_bytes(nbytes)
- # GAE fix, benadida
- #elif os.path.exists(devname):
- # # Many OSes support a /dev/urandom device
- # try:
- # f=open(devname)
- # data=f.read(nbytes)
- # f.close()
- # except IOError, (num, msg):
- # if num!=2: raise IOError, (num, msg)
- # # If the file wasn't found, ignore the error
- if data:
- self._addBytes(data)
- # Entropy estimate: The number of bits of
- # data obtained from the random source.
- self._updateEntropyEstimate(8*len(data))
- self.stir_n() # Wash the random pool
-
- def randomize(self, N=0):
- """randomize(N:int)
- use the class entropy source to get some entropy data.
- This is overridden by KeyboardRandomize().
- """
- return self._randomize(N)
-
- def stir_n(self, N = STIRNUM):
- """stir_n(N)
- stirs the random pool N times
- """
- for i in xrange(N):
- self.stir()
-
- def stir (self, s = ''):
- """stir(s:string)
- Mix up the randomness pool. This will call add_event() twice,
- but out of paranoia the entropy attribute will not be
- increased. The optional 's' parameter is a string that will
- be hashed with the randomness pool.
- """
-
- entropy=self.entropy # Save inital entropy value
- self.add_event()
-
- # Loop over the randomness pool: hash its contents
- # along with a counter, and add the resulting digest
- # back into the pool.
- for i in range(self.bytes / self._hash().digest_size):
- h = self._hash(self._randpool)
- h.update(str(self.__counter) + str(i) + str(self._addPos) + s)
- self._addBytes( h.digest() )
- self.__counter = (self.__counter + 1) & 0xFFFFffffL
-
- self._addPos, self._getPos = 0, self._hash().digest_size
- self.add_event()
-
- # Restore the old value of the entropy.
- self.entropy=entropy
-
-
- def get_bytes (self, N):
- """get_bytes(N:int) : string
- Return N bytes of random data.
- """
-
- s=''
- i, pool = self._getPos, self._randpool
- h=self._hash()
- dsize = self._hash().digest_size
- num = N
- while num > 0:
- h.update( self._randpool[i:i+dsize] )
- s = s + h.digest()
- num = num - dsize
- i = (i + dsize) % self.bytes
- if i>1, bits+1
- if bits>8: bits=8
-
- self._event1, self._event2 = event, self._event1
-
- self._updateEntropyEstimate(bits)
- return bits
-
- # Private functions
- def _noise(self):
- # Adds a bit of noise to the random pool, by adding in the
- # current time and CPU usage of this process.
- # The difference from the previous call to _noise() is taken
- # in an effort to estimate the entropy.
- t=time.time()
- delta = (t - self._lastcounter)/self._ticksize*1e6
- self._lastcounter = t
- self._addBytes(long_to_bytes(long(1000*time.time())))
- self._addBytes(long_to_bytes(long(1000*time.clock())))
- self._addBytes(long_to_bytes(long(1000*time.time())))
- self._addBytes(long_to_bytes(long(delta)))
-
- # Reduce delta to a maximum of 8 bits so we don't add too much
- # entropy as a result of this call.
- delta=delta % 0xff
- return int(delta)
-
-
- def _measureTickSize(self):
- # _measureTickSize() tries to estimate a rough average of the
- # resolution of time that you can see from Python. It does
- # this by measuring the time 100 times, computing the delay
- # between measurements, and taking the median of the resulting
- # list. (We also hash all the times and add them to the pool)
- interval = [None] * 100
- h = self._hash(`(id(self),id(interval))`)
-
- # Compute 100 differences
- t=time.time()
- h.update(`t`)
- i = 0
- j = 0
- while i < 100:
- t2=time.time()
- h.update(`(i,j,t2)`)
- j += 1
- delta=int((t2-t)*1e6)
- if delta:
- interval[i] = delta
- i += 1
- t=t2
-
- # Take the median of the array of intervals
- interval.sort()
- self._ticksize=interval[len(interval)/2]
- h.update(`(interval,self._ticksize)`)
- # mix in the measurement times and wash the random pool
- self.stir(h.digest())
-
- def _addBytes(self, s):
- "XOR the contents of the string S into the random pool"
- i, pool = self._addPos, self._randpool
- for j in range(0, len(s)):
- pool[i]=pool[i] ^ ord(s[j])
- i=(i+1) % self.bytes
- self._addPos = i
-
- # Deprecated method names: remove in PCT 2.1 or later.
- def getBytes(self, N):
- warnings.warn("getBytes() method replaced by get_bytes()",
- DeprecationWarning)
- return self.get_bytes(N)
-
- def addEvent (self, event, s=""):
- warnings.warn("addEvent() method replaced by add_event()",
- DeprecationWarning)
- return self.add_event(s + str(event))
-
-class PersistentRandomPool (RandomPool):
- def __init__ (self, filename=None, *args, **kwargs):
- RandomPool.__init__(self, *args, **kwargs)
- self.filename = filename
- if filename:
- try:
- # the time taken to open and read the file might have
- # a little disk variability, modulo disk/kernel caching...
- f=open(filename, 'rb')
- self.add_event()
- data = f.read()
- self.add_event()
- # mix in the data from the file and wash the random pool
- self.stir(data)
- f.close()
- except IOError:
- # Oh, well; the file doesn't exist or is unreadable, so
- # we'll just ignore it.
- pass
-
- def save(self):
- if self.filename == "":
- raise ValueError, "No filename set for this object"
- # wash the random pool before save, provides some forward secrecy for
- # old values of the pool.
- self.stir_n()
- f=open(self.filename, 'wb')
- self.add_event()
- f.write(self._randpool.tostring())
- f.close()
- self.add_event()
- # wash the pool again, provide some protection for future values
- self.stir()
-
-# non-echoing Windows keyboard entry
-_kb = 0
-if not _kb:
- try:
- import msvcrt
- class KeyboardEntry:
- def getch(self):
- c = msvcrt.getch()
- if c in ('\000', '\xe0'):
- # function key
- c += msvcrt.getch()
- return c
- def close(self, delay = 0):
- if delay:
- time.sleep(delay)
- while msvcrt.kbhit():
- msvcrt.getch()
- _kb = 1
- except:
- pass
-
-# non-echoing Posix keyboard entry
-if not _kb:
- try:
- import termios
- class KeyboardEntry:
- def __init__(self, fd = 0):
- self._fd = fd
- self._old = termios.tcgetattr(fd)
- new = termios.tcgetattr(fd)
- new[3]=new[3] & ~termios.ICANON & ~termios.ECHO
- termios.tcsetattr(fd, termios.TCSANOW, new)
- def getch(self):
- termios.tcflush(0, termios.TCIFLUSH) # XXX Leave this in?
- return os.read(self._fd, 1)
- def close(self, delay = 0):
- if delay:
- time.sleep(delay)
- termios.tcflush(self._fd, termios.TCIFLUSH)
- termios.tcsetattr(self._fd, termios.TCSAFLUSH, self._old)
- _kb = 1
- except:
- pass
-
-class KeyboardRandomPool (PersistentRandomPool):
- def __init__(self, *args, **kwargs):
- PersistentRandomPool.__init__(self, *args, **kwargs)
-
- def randomize(self, N = 0):
- "Adds N bits of entropy to random pool. If N is 0, fill up pool."
- import os, string, time
- if N <= 0:
- bits = self.bits - self.entropy
- else:
- bits = N*8
- if bits == 0:
- return
- print bits,'bits of entropy are now required. Please type on the keyboard'
- print 'until enough randomness has been accumulated.'
- kb = KeyboardEntry()
- s='' # We'll save the characters typed and add them to the pool.
- hash = self._hash
- e = 0
- try:
- while e < bits:
- temp=str(bits-e).rjust(6)
- os.write(1, temp)
- s=s+kb.getch()
- e += self.add_event(s)
- os.write(1, 6*chr(8))
- self.add_event(s+hash.new(s).digest() )
- finally:
- kb.close()
- print '\n\007 Enough. Please wait a moment.\n'
- self.stir_n() # wash the random pool.
- kb.close(4)
-
-if __name__ == '__main__':
- pool = RandomPool()
- print 'random pool entropy', pool.entropy, 'bits'
- pool.add_event('something')
- print `pool.get_bytes(100)`
- import tempfile, os
- fname = tempfile.mktemp()
- pool = KeyboardRandomPool(filename=fname)
- print 'keyboard random pool entropy', pool.entropy, 'bits'
- pool.randomize()
- print 'keyboard random pool entropy', pool.entropy, 'bits'
- pool.randomize(128)
- pool.save()
- saved = open(fname, 'rb').read()
- print 'saved', `saved`
- print 'pool ', `pool._randpool.tostring()`
- newpool = PersistentRandomPool(fname)
- print 'persistent random pool entropy', pool.entropy, 'bits'
- os.remove(fname)
diff --git a/helios/crypto/utils.py b/helios/crypto/utils.py
index dd395a598..2fcce307f 100644
--- a/helios/crypto/utils.py
+++ b/helios/crypto/utils.py
@@ -1,23 +1,31 @@
"""
Crypto Utils
"""
+import base64
+import math
+
+from Crypto.Hash import SHA256
+from Crypto.Random.random import StrongRandom
+
+random = StrongRandom()
+
+
+def random_mpz_lt(maximum, strong_random=random):
+ n_bits = int(math.floor(math.log(maximum, 2)))
+ res = strong_random.getrandbits(n_bits)
+ while res >= maximum:
+ res = strong_random.getrandbits(n_bits)
+ return res
+
+
+random.mpz_lt = random_mpz_lt
-import hmac, base64, json
-from hashlib import sha256
-
def hash_b64(s):
- """
- hash the string using sha1 and produce a base64 output
- removes the trailing "="
- """
- hasher = sha256(s)
- result= base64.b64encode(hasher.digest())[:-1]
- return result
-
-def to_json(d):
- return json.dumps(d, sort_keys=True)
-
-def from_json(json_str):
- if not json_str: return None
- return json.loads(json_str)
+ """
+ hash the string using sha256 and produce a base64 output
+ removes the trailing "="
+ """
+ hasher = SHA256.new(s.encode('utf-8'))
+ result = base64.b64encode(hasher.digest())[:-1].decode('ascii')
+ return result
diff --git a/helios/datatypes/__init__.py b/helios/datatypes/__init__.py
index b0ede25f2..574f8eb93 100644
--- a/helios/datatypes/__init__.py
+++ b/helios/datatypes/__init__.py
@@ -25,6 +25,7 @@
# but is not necessary for full JSON-LD objects.
LDObject.deserialize(json_string, type=...)
"""
+import importlib
from helios import utils
from helios.crypto import utils as cryptoutils
@@ -33,32 +34,32 @@
## utility function
##
def recursiveToDict(obj):
- if obj == None:
+ if obj is None:
return None
- if type(obj) == list:
+ if isinstance(obj, list):
return [recursiveToDict(el) for el in obj]
else:
return obj.toDict()
def get_class(datatype):
# already done?
- if not isinstance(datatype, basestring):
+ if not isinstance(datatype, str):
return 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)
+ dynamic_module = importlib.import_module("helios.datatypes." + (".".join(parsed_datatype[:-1])))
if not dynamic_module:
- raise Exception("no module for %s" % datatpye)
+ raise Exception("no module for %s" % datatype)
# go down the attributes to get to the class
try:
dynamic_ptr = dynamic_module
- for attr in parsed_datatype[1:]:
+ for attr in parsed_datatype[-1:]:
dynamic_ptr = getattr(dynamic_ptr, attr)
dynamic_cls = dynamic_ptr
except AttributeError:
@@ -119,7 +120,7 @@ def __init__(self, wrapped_obj):
@classmethod
def instantiate(cls, obj, datatype=None):
- "FIXME: should datatype override the object's internal datatype? probably not"
+ """FIXME: should datatype override the object's internal datatype? probably not"""
if isinstance(obj, LDObject):
return obj
@@ -130,7 +131,7 @@ def instantiate(cls, obj, datatype=None):
raise Exception("no datatype found")
# nulls
- if obj == None:
+ if obj is None:
return None
# the class
@@ -149,9 +150,11 @@ def _setattr_wrapped(self, attr, val):
setattr(self.wrapped_obj, attr, val)
def loadData(self):
- "load data using from the wrapped object"
+ """
+ load data using from the wrapped object
+ """
# go through the subfields and instantiate them too
- for subfield_name, subfield_type in self.STRUCTURED_FIELDS.iteritems():
+ for subfield_name, subfield_type in self.STRUCTURED_FIELDS.items():
self.structured_fields[subfield_name] = self.instantiate(self._getattr_wrapped(subfield_name), datatype = subfield_type)
def loadDataFromDict(self, d):
@@ -160,7 +163,7 @@ def loadDataFromDict(self, d):
"""
# the structured fields
- structured_fields = self.STRUCTURED_FIELDS.keys()
+ structured_fields = list(self.STRUCTURED_FIELDS.keys())
# go through the fields and set them properly
# on the newly instantiated object
@@ -171,7 +174,7 @@ def loadDataFromDict(self, d):
self.structured_fields[f] = sub_ld_object
# set the field on the wrapped object too
- if sub_ld_object != None:
+ if sub_ld_object is not None:
self._setattr_wrapped(f, sub_ld_object.wrapped_obj)
else:
self._setattr_wrapped(f, None)
@@ -190,12 +193,12 @@ def toDict(self, alternate_fields=None, complete=False):
fields = self.FIELDS
if not self.structured_fields:
- if self.wrapped_obj.alias != None:
+ if self.wrapped_obj.alias is not None:
fields = self.ALIASED_VOTER_FIELDS
for f in (alternate_fields or fields):
# is it a structured subfield?
- if self.structured_fields.has_key(f):
+ if f in self.structured_fields:
val[f] = recursiveToDict(self.structured_fields[f])
else:
val[f] = self.process_value_out(f, self._getattr_wrapped(f))
@@ -214,7 +217,7 @@ def toDict(self, alternate_fields=None, complete=False):
@classmethod
def fromDict(cls, d, type_hint=None):
# null objects
- if d == None:
+ if d is None:
return None
# the LD type is either in d or in type_hint
@@ -248,11 +251,11 @@ def process_value_in(self, field_name, field_value):
"""
process some fields on the way into the object
"""
- if field_value == None:
+ if field_value is None:
return None
val = self._process_value_in(field_name, field_value)
- if val != None:
+ if val is not None:
return val
else:
return field_value
@@ -264,23 +267,25 @@ def process_value_out(self, field_name, field_value):
"""
process some fields on the way out of the object
"""
- if field_value == None:
+ if field_value is None:
return None
val = self._process_value_out(field_name, field_value)
- if val != None:
+ if val is not None:
return val
else:
return field_value
def _process_value_out(self, field_name, field_value):
+ if isinstance(field_value, bytes):
+ return field_value.decode('utf-8')
return None
-
+
def __eq__(self, other):
if not hasattr(self, 'uuid'):
- return super(LDObject,self) == other
+ return super(LDObject, self) == other
- return other != None and self.uuid == other.uuid
+ return other is not None and self.uuid == other.uuid
class BaseArrayOfObjects(LDObject):
diff --git a/helios/datatypes/djangofield.py b/helios/datatypes/djangofield.py
index e0eb1b4fb..a299ace6c 100644
--- a/helios/datatypes/djangofield.py
+++ b/helios/datatypes/djangofield.py
@@ -6,15 +6,12 @@
and adapted to LDObject
"""
-import datetime
-import json
from django.db import models
-from django.db.models import signals
-from django.conf import settings
-from django.core.serializers.json import DjangoJSONEncoder
+from helios import utils
from . import LDObject
+
class LDObjectField(models.TextField):
"""
LDObject is a generic textfield that neatly serializes/unserializes
@@ -23,9 +20,6 @@ class LDObjectField(models.TextField):
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, type_hint=None, **kwargs):
self.type_hint = type_hint
super(LDObjectField, self).__init__(**kwargs)
@@ -34,35 +28,29 @@ def to_python(self, value):
"""Convert our string value to LDObject after we load it from the DB"""
# did we already convert this?
- if not isinstance(value, basestring):
+ if not isinstance(value, str):
return value
- if value == None:
- return None
+ return self.from_db_value(value)
+ # noinspection PyUnusedLocal
+ def from_db_value(self, value, *args, **kwargs):
# in some cases, we're loading an existing array or dict,
- # we skip this part but instantiate the LD object
- if isinstance(value, basestring):
- try:
- parsed_value = json.loads(value)
- except:
- raise Exception("value is not JSON parseable, that's bad news")
- else:
- parsed_value = value
-
- if parsed_value != None:
- "we give the wrapped object back because we're not dealing with serialization types"
- return_val = LDObject.fromDict(parsed_value, type_hint = self.type_hint).wrapped_obj
- return return_val
- else:
+ # from_json takes care of this duality
+ parsed_value = utils.from_json(value)
+ if parsed_value is None:
return None
+ # we give the wrapped object back because we're not dealing with serialization types
+ return_val = LDObject.fromDict(parsed_value, type_hint=self.type_hint).wrapped_obj
+ return return_val
+
def get_prep_value(self, value):
"""Convert our JSON object to a string before we save"""
- if isinstance(value, basestring):
+ if isinstance(value, str):
return value
- if value == None:
+ if value is None:
return None
# instantiate the proper LDObject to dump it appropriately
@@ -71,4 +59,4 @@ def get_prep_value(self, value):
def value_to_string(self, obj):
value = self._get_val_from_obj(obj)
- return self.get_db_prep_value(value)
+ return self.get_db_prep_value(value, None)
diff --git a/helios/datatypes/legacy.py b/helios/datatypes/legacy.py
index c0a24ffc2..d469b4418 100644
--- a/helios/datatypes/legacy.py
+++ b/helios/datatypes/legacy.py
@@ -77,7 +77,7 @@ def toDict(self, complete=False):
"""
depending on whether the voter is aliased, use different fields
"""
- if self.wrapped_obj.alias != None:
+ if self.wrapped_obj.alias is not None:
return super(Voter, self).toDict(self.ALIASED_VOTER_FIELDS, complete = complete)
else:
return super(Voter,self).toDict(complete = complete)
diff --git a/helios/datetimewidget.py b/helios/datetimewidget.py
index 5a9e0d40a..dfd7ec04a 100644
--- a/helios/datetimewidget.py
+++ b/helios/datetimewidget.py
@@ -14,7 +14,7 @@
from django.utils.safestring import mark_safe
# DATETIMEWIDGET
-calbtn = u'''
+calbtn = '''
'''
class DateTimeWidget(forms.widgets.TextInput):
+ template_name = ''
+
class Media:
css = {
'all': (
@@ -49,13 +51,13 @@ def render(self, name, value, attrs=None):
except:
final_attrs['value'] = \
force_unicode(value)
- if not final_attrs.has_key('id'):
- final_attrs['id'] = u'%s_id' % (name)
+ if 'id' not in final_attrs:
+ final_attrs['id'] = '%s_id' % (name)
id = final_attrs['id']
jsdformat = self.dformat #.replace('%', '%%')
cal = calbtn % (settings.MEDIA_URL, id, id, jsdformat, id)
- a = u'%s%s' % (forms.util.flatatt(final_attrs), self.media, cal)
+ a = '%s%s' % (forms.util.flatatt(final_attrs), self.media, cal)
return mark_safe(a)
def value_from_datadict(self, data, files, name):
@@ -82,12 +84,12 @@ def _has_changed(self, initial, data):
Copy of parent's method, but modify value with strftime function before final comparsion
"""
if data is None:
- data_value = u''
+ data_value = ''
else:
data_value = data
if initial is None:
- initial_value = u''
+ initial_value = ''
else:
initial_value = initial
diff --git a/helios/election_url_names.py b/helios/election_url_names.py
new file mode 100644
index 000000000..eff9a5ea2
--- /dev/null
+++ b/helios/election_url_names.py
@@ -0,0 +1,62 @@
+ELECTION_HOME="election@home"
+ELECTION_VIEW="election@view"
+ELECTION_META="election@meta"
+ELECTION_EDIT="election@edit"
+ELECTION_SCHEDULE="election@schedule"
+ELECTION_EXTEND="election@extend"
+ELECTION_ARCHIVE="election@archive"
+ELECTION_COPY="election@copy"
+ELECTION_BADGE="election@badge"
+
+ELECTION_TRUSTEES_HOME="election@trustees"
+ELECTION_TRUSTEES_VIEW="election@trustees@view"
+ELECTION_TRUSTEES_NEW="election@trustees@new"
+ELECTION_TRUSTEES_ADD_HELIOS="election@trustees@add-helios"
+ELECTION_TRUSTEES_DELETE="election@trustees@delete"
+
+ELECTION_TRUSTEE_HOME="election@trustee"
+ELECTION_TRUSTEE_SEND_URL="election@trustee@send-url"
+ELECTION_TRUSTEE_KEY_GENERATOR="election@trustee@key-generator"
+ELECTION_TRUSTEE_CHECK_SK="election@trustee@check-sk"
+ELECTION_TRUSTEE_UPLOAD_PK="election@trustee@upload-pk"
+ELECTION_TRUSTEE_DECRYPT_AND_PROVE="election@trustee@decrypt-and-prove"
+ELECTION_TRUSTEE_UPLOAD_DECRYPTION="election@trustee@upload-decryption"
+
+ELECTION_RESULT="election@result"
+ELECTION_RESULT_PROOF="election@result@proof"
+ELECTION_BBOARD="election@bboard"
+ELECTION_AUDITED_BALLOTS="election@audited-ballots"
+
+ELECTION_GET_RANDOMNESS="election@get-randomness"
+ELECTION_ENCRYPT_BALLOT="election@encrypt-ballot"
+ELECTION_QUESTIONS="election@questions"
+ELECTION_SET_REG="election@set-reg"
+ELECTION_SET_FEATURED="election@set-featured"
+ELECTION_SAVE_QUESTIONS="election@save-questions"
+ELECTION_REGISTER="election@register"
+ELECTION_FREEZE="election@freeze"
+
+ELECTION_COMPUTE_TALLY="election@compute-tally"
+ELECTION_COMBINE_DECRYPTIONS="election@combine-decryptions"
+ELECTION_RELEASE_RESULT="election@release-result"
+
+ELECTION_CAST="election@cast"
+ELECTION_CAST_CONFIRM="election@cast-confirm"
+ELECTION_PASSWORD_VOTER_LOGIN="election@password-voter-login"
+ELECTION_CAST_DONE="election@cast-done"
+
+ELECTION_POST_AUDITED_BALLOT="election@post-audited-ballot"
+
+ELECTION_VOTERS_HOME="election@voters"
+ELECTION_VOTERS_UPLOAD="election@voters@upload"
+ELECTION_VOTERS_UPLOAD_CANCEL="election@voters@upload-cancel"
+ELECTION_VOTERS_LIST="election@voters@list"
+ELECTION_VOTERS_LIST_PRETTY="election@voters@list-pretty"
+ELECTION_VOTERS_ELIGIBILITY="election@voters@eligibility"
+ELECTION_VOTERS_EMAIL="election@voters@email"
+ELECTION_VOTER="election@voter"
+ELECTION_VOTER_DELETE="election@voter@delete"
+
+ELECTION_BALLOTS_LIST="election@ballots@list"
+ELECTION_BALLOTS_VOTER="election@ballots@voter"
+ELECTION_BALLOTS_VOTER_LAST="election@ballots@voter@last"
diff --git a/helios/election_urls.py b/helios/election_urls.py
index 6622c5568..03e447308 100644
--- a/helios/election_urls.py
+++ b/helios/election_urls.py
@@ -4,91 +4,99 @@
Ben Adida (ben@adida.net)
"""
-from django.conf.urls import *
+from django.urls import path, re_path
-from helios.views import *
+from helios import views
+from helios import election_url_names as names
-urlpatterns = patterns('',
+urlpatterns = [
# election data that is cryptographically verified
- (r'^$', one_election),
+ path('', views.one_election, name=names.ELECTION_HOME),
# metadata that need not be verified
- (r'^/meta$', one_election_meta),
+ path('/meta', views.one_election_meta, name=names.ELECTION_META),
# edit election params
- (r'^/edit$', one_election_edit),
- (r'^/schedule$', one_election_schedule),
- (r'^/extend$', one_election_extend),
- (r'^/archive$', one_election_archive),
- (r'^/copy$', one_election_copy),
+ path('/edit', views.one_election_edit, name=names.ELECTION_EDIT),
+ path('/schedule', views.one_election_schedule, name=names.ELECTION_SCHEDULE),
+ path('/extend', views.one_election_extend, name=names.ELECTION_EXTEND),
+ path('/archive', views.one_election_archive, name=names.ELECTION_ARCHIVE),
+ path('/copy', views.one_election_copy, name=names.ELECTION_COPY),
# badge
- (r'^/badge$', election_badge),
+ path('/badge', views.election_badge, name=names.ELECTION_BADGE),
# adding trustees
- (r'^/trustees/$', list_trustees),
- (r'^/trustees/view$', list_trustees_view),
- (r'^/trustees/new$', new_trustee),
- (r'^/trustees/add-helios$', new_trustee_helios),
- (r'^/trustees/delete$', delete_trustee),
+ path('/trustees/', views.list_trustees, name=names.ELECTION_TRUSTEES_HOME),
+ path('/trustees/view', views.list_trustees_view, name=names.ELECTION_TRUSTEES_VIEW),
+ path('/trustees/new', views.new_trustee, name=names.ELECTION_TRUSTEES_NEW),
+ path('/trustees/add-helios', views.new_trustee_helios, name=names.ELECTION_TRUSTEES_ADD_HELIOS),
+ path('/trustees/delete', views.delete_trustee, name=names.ELECTION_TRUSTEES_DELETE),
# trustee pages
- (r'^/trustees/(?P[^/]+)/home$', trustee_home),
- (r'^/trustees/(?P[^/]+)/sendurl$', trustee_send_url),
- (r'^/trustees/(?P[^/]+)/keygenerator$', trustee_keygenerator),
- (r'^/trustees/(?P[^/]+)/check-sk$', trustee_check_sk),
- (r'^/trustees/(?P[^/]+)/upoad-pk$', trustee_upload_pk),
- (r'^/trustees/(?P[^/]+)/decrypt-and-prove$', trustee_decrypt_and_prove),
- (r'^/trustees/(?P[^/]+)/upload-decryption$', trustee_upload_decryption),
+ path('/trustees//home',
+ views.trustee_home, name=names.ELECTION_TRUSTEE_HOME),
+ path('/trustees//sendurl',
+ views.trustee_send_url, name=names.ELECTION_TRUSTEE_SEND_URL),
+ path('/trustees//keygenerator',
+ views.trustee_keygenerator, name=names.ELECTION_TRUSTEE_KEY_GENERATOR),
+ path('/trustees//check-sk',
+ views.trustee_check_sk, name=names.ELECTION_TRUSTEE_CHECK_SK),
+ path('/trustees//upoad-pk',
+ views.trustee_upload_pk, name=names.ELECTION_TRUSTEE_UPLOAD_PK),
+ path('/trustees//decrypt-and-prove',
+ views.trustee_decrypt_and_prove, name=names.ELECTION_TRUSTEE_DECRYPT_AND_PROVE),
+ path('/trustees//upload-decryption',
+ views.trustee_upload_decryption, name=names.ELECTION_TRUSTEE_UPLOAD_DECRYPTION),
# election voting-process actions
- (r'^/view$', one_election_view),
- (r'^/result$', one_election_result),
- (r'^/result_proof$', one_election_result_proof),
- # (r'^/bboard$', one_election_bboard),
- (r'^/audited-ballots/$', one_election_audited_ballots),
+ path('/view', views.one_election_view, name=names.ELECTION_VIEW),
+ path('/result', views.one_election_result, name=names.ELECTION_RESULT),
+ path('/result_proof', views.one_election_result_proof, name=names.ELECTION_RESULT_PROOF),
+ # url(r'^/bboard$', views.one_election_bboard, name=names.ELECTION_BBOARD),
+ path('/audited-ballots/', views.one_election_audited_ballots, name=names.ELECTION_AUDITED_BALLOTS),
# get randomness
- (r'^/get-randomness$', get_randomness),
+ path('/get-randomness', views.get_randomness, name=names.ELECTION_GET_RANDOMNESS),
# server-side encryption
- (r'^/encrypt-ballot$', encrypt_ballot),
+ path('/encrypt-ballot', views.encrypt_ballot, name=names.ELECTION_ENCRYPT_BALLOT),
# construct election
- (r'^/questions$', one_election_questions),
- (r'^/set_reg$', one_election_set_reg),
- (r'^/set_featured$', one_election_set_featured),
- (r'^/save_questions$', one_election_save_questions),
- (r'^/register$', one_election_register),
- (r'^/freeze$', one_election_freeze), # includes freeze_2 as POST target
+ path('/questions', views.one_election_questions, name=names.ELECTION_QUESTIONS),
+ path('/set_reg', views.one_election_set_reg, name=names.ELECTION_SET_REG),
+ path('/set_featured', views.one_election_set_featured, name=names.ELECTION_SET_FEATURED),
+ path('/save_questions', views.one_election_save_questions, name=names.ELECTION_SAVE_QUESTIONS),
+ path('/register', views.one_election_register, name=names.ELECTION_REGISTER),
+ path('/freeze', views.one_election_freeze, name=names.ELECTION_FREEZE), # includes freeze_2 as POST target
# computing tally
- (r'^/compute_tally$', one_election_compute_tally),
- (r'^/combine_decryptions$', combine_decryptions),
- (r'^/release_result$', release_result),
+ path('/compute_tally', views.one_election_compute_tally, name=names.ELECTION_COMPUTE_TALLY),
+ path('/combine_decryptions', views.combine_decryptions, name=names.ELECTION_COMBINE_DECRYPTIONS),
+ path('/release_result', views.release_result, name=names.ELECTION_RELEASE_RESULT),
# casting a ballot before we know who the voter is
- (r'^/cast$', one_election_cast),
- (r'^/cast_confirm$', one_election_cast_confirm),
- (r'^/password_voter_login$', password_voter_login),
- (r'^/cast_done$', one_election_cast_done),
+ path('/cast', views.one_election_cast, name=names.ELECTION_CAST),
+ path('/cast_confirm', views.one_election_cast_confirm, name=names.ELECTION_CAST_CONFIRM),
+ path('/password_voter_login', views.password_voter_login, name=names.ELECTION_PASSWORD_VOTER_LOGIN),
+ path('/cast_done', views.one_election_cast_done, name=names.ELECTION_CAST_DONE),
# post audited ballot
- (r'^/post-audited-ballot', post_audited_ballot),
+ re_path(r'^/post-audited-ballot', views.post_audited_ballot, name=names.ELECTION_POST_AUDITED_BALLOT),
# managing voters
- (r'^/voters/$', voter_list),
- (r'^/voters/upload$', voters_upload),
- (r'^/voters/upload-cancel$', voters_upload_cancel),
- (r'^/voters/list$', voters_list_pretty),
- (r'^/voters/eligibility$', voters_eligibility),
- (r'^/voters/email$', voters_email),
- (r'^/voters/(?P[^/]+)$', one_voter),
- (r'^/voters/(?P[^/]+)/delete$', voter_delete),
+ path('/voters/', views.voter_list, name=names.ELECTION_VOTERS_LIST),
+ path('/voters/upload', views.voters_upload, name=names.ELECTION_VOTERS_UPLOAD),
+ path('/voters/upload-cancel', views.voters_upload_cancel, name=names.ELECTION_VOTERS_UPLOAD_CANCEL),
+ path('/voters/list', views.voters_list_pretty, name=names.ELECTION_VOTERS_LIST_PRETTY),
+ path('/voters/eligibility', views.voters_eligibility, name=names.ELECTION_VOTERS_ELIGIBILITY),
+ path('/voters/email', views.voters_email, name=names.ELECTION_VOTERS_EMAIL),
+ path('/voters/', views.one_voter, name=names.ELECTION_VOTER),
+ path('/voters//delete', views.voter_delete, name=names.ELECTION_VOTER_DELETE),
# ballots
- (r'^/ballots/$', ballot_list),
- (r'^/ballots/(?P[^/]+)/all$', voter_votes),
- (r'^/ballots/(?P[^/]+)/last$', voter_last_vote),
+ path('/ballots/', views.ballot_list, name=names.ELECTION_BALLOTS_LIST),
+ path('/ballots//all', views.voter_votes, name=names.ELECTION_BALLOTS_VOTER),
+ path('/ballots//last', views.voter_last_vote, name=names.ELECTION_BALLOTS_VOTER_LAST),
-)
+]
diff --git a/helios/fields.py b/helios/fields.py
index cf2ad6c3e..8d8e885fe 100644
--- a/helios/fields.py
+++ b/helios/fields.py
@@ -1,9 +1,9 @@
-from time import strptime, strftime
import datetime
-from django import forms
-from django.db import models
+
from django.forms import fields
-from widgets import SplitSelectDateTimeWidget
+
+from .widgets import SplitSelectDateTimeWidget
+
class SplitDateTimeField(fields.MultiValueField):
widget = SplitSelectDateTimeWidget
diff --git a/helios/fixtures/election.json b/helios/fixtures/election.json
deleted file mode 100644
index cd7a61655..000000000
--- a/helios/fixtures/election.json
+++ /dev/null
@@ -1,25 +0,0 @@
-[{"pk": 1000,
- "model": "helios.election",
- "fields":
- {
- "admin": 1,
- "uuid" : "206ef039-05c9-4e9c-bb8f-963da50c08d4",
- "short_name" : "test",
- "name" : "Test Election",
- "election_type" : "election",
- "use_advanced_audit_features" : true,
- "created_at" : "2013-02-22 12:00:00",
- "modified_at" : "2013-02-22 12:00:00",
- "private_p" : false,
- "description" : "test description",
- "public_key" : null,
- "private_key" : null,
- "questions" : [],
- "eligibility": null,
- "openreg": true,
- "featured_p": false,
- "use_voter_aliases" : false,
- "cast_url" : "/helios/elections/206ef039-05c9-4e9c-bb8f-963da50c08d4/cast"
- }
- }
- ]
diff --git a/helios/fixtures/users.json b/helios/fixtures/users.json
index c588ce475..c6e18b53d 100644
--- a/helios/fixtures/users.json
+++ b/helios/fixtures/users.json
@@ -1 +1,40 @@
-[{"pk": 1, "model": "helios_auth.user", "fields": {"info": "{}", "user_id": "ben@adida.net", "name": "Ben Adida", "user_type": "google", "token": null, "admin_p": false}},{"pk": 2, "model": "helios_auth.user", "fields": {"info": "{}", "user_id": "12345", "name": "Ben Adida", "user_type": "facebook", "token": {"access_token":"1234"}, "admin_p": false}}]
\ No newline at end of file
+[
+ {
+ "pk": 1,
+ "model": "helios_auth.user",
+ "fields": {
+ "info": "{}",
+ "user_id": "ben@adida.net",
+ "name": "Ben Adida",
+ "user_type": "google",
+ "token": null,
+ "admin_p": false
+ }
+ },
+ {
+ "pk": 2,
+ "model": "helios_auth.user",
+ "fields": {
+ "info": "{}",
+ "user_id": "12345",
+ "name": "Ben Adida",
+ "user_type": "facebook",
+ "token": {
+ "access_token": "1234"
+ },
+ "admin_p": false
+ }
+ },
+ {
+ "pk": 3,
+ "model": "helios_auth.user",
+ "fields": {
+ "info": "{}",
+ "user_id": "mccio@github.com",
+ "name": "Marco Ciotola",
+ "user_type": "google",
+ "token": null,
+ "admin_p": true
+ }
+ }
+]
\ No newline at end of file
diff --git a/helios/fixtures/voter-badfile.csv b/helios/fixtures/voter-badfile.csv
index fd674a999..cc858ace2 100644
--- a/helios/fixtures/voter-badfile.csv
+++ b/helios/fixtures/voter-badfile.csv
@@ -1,5 +1,5 @@
-Ben78@adida.net,Ben78 Adida
- benadida5,ben5@adida.net , Ben5 Adida
-benadida6,ben6@adida.net,Ben6 Adida
-benadida7,ben7@adida.net,Ben7 Adida
-ernesto,helios-testing-ernesto@adida.net,Erñesto Testing Helios
\ No newline at end of file
+password,Ben78@adida.net,Ben78 Adida
+password, benadida5,ben5@adida.net , Ben5 Adida
+password,benadida6,ben6@adida.net,Ben6 Adida
+password,benadida7,ben7@adida.net,Ben7 Adida
+password,ernesto,helios-testing-ernesto@adida.net,Erñesto Testing Helios
\ No newline at end of file
diff --git a/helios/fixtures/voter-file-latin1.csv b/helios/fixtures/voter-file-latin1.csv
new file mode 100644
index 000000000..4c2e42a48
--- /dev/null
+++ b/helios/fixtures/voter-file-latin1.csv
@@ -0,0 +1,4 @@
+password, benadida5,ben5@adida.net , Ben5 Adida
+password,benadida6,ben6@adida.net,Ben6 Adida
+password,benadida7,ben7@adida.net,Ben7 Adida
+password,testlatin1,Test Latin1,J�NIO LUIZ CORREIA J�NIOR
diff --git a/helios/fixtures/voter-file.csv b/helios/fixtures/voter-file.csv
index b94bd1a4f..70795def3 100644
--- a/helios/fixtures/voter-file.csv
+++ b/helios/fixtures/voter-file.csv
@@ -1,4 +1,4 @@
- benadida5,ben5@adida.net , Ben5 Adida
-benadida6,ben6@adida.net,Ben6 Adida
-benadida7,ben7@adida.net,Ben7 Adida
-ernesto,helios-testing-ernesto@adida.net,Erñesto Testing Helios
\ No newline at end of file
+password, benadida5,ben5@adida.net , Ben5 Adida
+password,benadida6,ben6@adida.net,Ben6 Adida
+password,benadida7,ben7@adida.net,Ben7 Adida
+password,ernesto,helios-testing-ernesto@adida.net,Erñesto Testing Helios
\ No newline at end of file
diff --git a/helios/forms.py b/helios/forms.py
index cb10cfab8..d79196758 100644
--- a/helios/forms.py
+++ b/helios/forms.py
@@ -3,11 +3,12 @@
"""
from django import forms
-from models import Election
-from widgets import *
-from fields import *
from django.conf import settings
+from .fields import SplitDateTimeField
+from .models import Election
+from .widgets import SplitSelectDateTimeWidget
+
class ElectionForm(forms.Form):
short_name = forms.SlugField(max_length=40, help_text='no spaces, will be part of the URL for your election, e.g. my-club-2010')
diff --git a/helios/management/commands/helios_trustee_decrypt.py b/helios/management/commands/helios_trustee_decrypt.py
index 3dc75a4af..478833020 100644
--- a/helios/management/commands/helios_trustee_decrypt.py
+++ b/helios/management/commands/helios_trustee_decrypt.py
@@ -8,12 +8,10 @@
2010-05-22
"""
-from django.core.management.base import BaseCommand, CommandError
-import csv, datetime
+from django.core.management.base import BaseCommand
-from helios import utils as helios_utils
+from helios.models import Trustee
-from helios.models import *
class Command(BaseCommand):
args = ''
diff --git a/helios/management/commands/load_voter_files.py b/helios/management/commands/load_voter_files.py
index 5b82285a6..1e3a79bee 100644
--- a/helios/management/commands/load_voter_files.py
+++ b/helios/management/commands/load_voter_files.py
@@ -8,12 +8,15 @@
2010-05-22
"""
-from django.core.management.base import BaseCommand, CommandError
-import csv, datetime
+import datetime
+
+import csv
+import uuid
+from django.core.management.base import BaseCommand
from helios import utils as helios_utils
+from helios.models import User, Voter, VoterFile
-from helios.models import *
##
## UTF8 craziness for CSV
@@ -25,44 +28,47 @@ def unicode_csv_reader(unicode_csv_data, dialect=csv.excel, **kwargs):
dialect=dialect, **kwargs)
for row in csv_reader:
# decode UTF-8 back to Unicode, cell by cell:
- yield [unicode(cell, 'utf-8') for cell in row]
+ yield [str(cell, 'utf-8') for cell in row]
+
def utf_8_encoder(unicode_csv_data):
for line in unicode_csv_data:
yield line.encode('utf-8')
-
+
+
def process_csv_file(election, f):
reader = unicode_csv_reader(f)
-
+
num_voters = 0
for voter in reader:
- # bad line
- if len(voter) < 1:
- continue
-
- num_voters += 1
- voter_id = voter[0]
- name = voter_id
- email = voter_id
-
- if len(voter) > 1:
- email = voter[1]
-
- if len(voter) > 2:
- name = voter[2]
-
- # create the user
- user = User.update_or_create(user_type='password', user_id=voter_id, info = {'password': helios_utils.random_string(10), 'email': email, 'name': name})
- user.save()
-
- # does voter for this user already exist
- voter = Voter.get_by_election_and_user(election, user)
-
- # create the voter
- if not voter:
- voter_uuid = str(uuid.uuid1())
- voter = Voter(uuid= voter_uuid, voter_type = 'password', voter_id = voter_id, name = name, election = election)
- voter.save()
+ # bad line
+ if len(voter) < 1:
+ continue
+
+ num_voters += 1
+ voter_id = voter[0]
+ name = voter_id
+ email = voter_id
+
+ if len(voter) > 1:
+ email = voter[1]
+
+ if len(voter) > 2:
+ name = voter[2]
+
+ # create the user
+ user = User.update_or_create(user_type='password', user_id=voter_id,
+ info={'password': helios_utils.random_string(10), 'email': email, 'name': name})
+ user.save()
+
+ # does voter for this user already exist
+ voter = Voter.get_by_election_and_user(election, user)
+
+ # create the voter
+ if not voter:
+ voter_uuid = str(uuid.uuid1())
+ voter = Voter(uuid=voter_uuid, voter_type='password', voter_id=voter_id, name=name, election=election)
+ voter.save()
return num_voters
@@ -70,7 +76,7 @@ def process_csv_file(election, f):
class Command(BaseCommand):
args = ''
help = 'load up voters from unprocessed voter files'
-
+
def handle(self, *args, **options):
# load up the voter files in order of last uploaded
files_to_process = VoterFile.objects.filter(processing_started_at=None).order_by('uploaded_at')
@@ -86,5 +92,3 @@ def handle(self, *args, **options):
file_to_process.processing_finished_at = datetime.datetime.utcnow()
file_to_process.num_voters = num_voters
file_to_process.save()
-
-
diff --git a/helios/management/commands/verify_cast_votes.py b/helios/management/commands/verify_cast_votes.py
index 5b7f39253..e2fab7186 100644
--- a/helios/management/commands/verify_cast_votes.py
+++ b/helios/management/commands/verify_cast_votes.py
@@ -6,12 +6,10 @@
2010-05-22
"""
-from django.core.management.base import BaseCommand, CommandError
-import csv, datetime
+from django.core.management.base import BaseCommand
-from helios import utils as helios_utils
+from helios.models import CastVote
-from helios.models import *
def get_cast_vote_to_verify():
# fixme: add "select for update" functionality here
diff --git a/helios/media/static_templates/question.html b/helios/media/static_templates/question.html
index 5e576916d..15b388253 100644
--- a/helios/media/static_templates/question.html
+++ b/helios/media/static_templates/question.html
@@ -4,7 +4,7 @@
{#if $T.admin_p}[
{#if $T.question$index > 0}^] [{#/if}
-x] [edit] {#/if}{$T.question$index + 1}. {$T.question.question} ({$T.question.choice_type}, select between {$T.question.min} and {#if $T.question.max != null}{$T.question.max}{#else}unlimited{#/if} answers, result type {$T.question.result_type}.)
+x] [edit] {#/if}{$T.question$index + 1}. {$T.question.question} ({$T.question.choice_type}, select between {$T.question.min} and {#if $T.question.max != null}{$T.question.max}{#else}unlimited{#/if} answers, result type {$T.question.result_type}{#if $T.question.randomize_answer_order}, random answer order{#/if}.)
{#foreach $T.question.answers as answer}
{$T.answer}
@@ -55,6 +55,13 @@
+
+
+Random Answer Order:
+
@@ -115,6 +122,15 @@
Add a Question:
+
+
+Random Answer Order:
+
+
+
diff --git a/helios/migrations/0001_initial.py b/helios/migrations/0001_initial.py
index cb2a41e67..886b37021 100644
--- a/helios/migrations/0001_initial.py
+++ b/helios/migrations/0001_initial.py
@@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
from django.db import models, migrations
+
+import helios.datatypes
import helios.datatypes.djangofield
import helios_auth.jsonfield
-import helios.datatypes
class Migration(migrations.Migration):
@@ -87,7 +87,7 @@ class Migration(migrations.Migration):
('result_proof', helios_auth.jsonfield.JSONField(null=True)),
('help_email', models.EmailField(max_length=75, null=True)),
('election_info_url', models.CharField(max_length=300, null=True)),
- ('admin', models.ForeignKey(to='helios_auth.User')),
+ ('admin', models.ForeignKey(to='helios_auth.User', on_delete=models.CASCADE)),
],
options={
'abstract': False,
@@ -100,7 +100,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('log', models.CharField(max_length=500)),
('at', models.DateTimeField(auto_now_add=True)),
- ('election', models.ForeignKey(to='helios.Election')),
+ ('election', models.ForeignKey(to='helios.Election', on_delete=models.CASCADE)),
],
options={
},
@@ -120,7 +120,7 @@ class Migration(migrations.Migration):
('pok', helios.datatypes.djangofield.LDObjectField(null=True)),
('decryption_factors', helios.datatypes.djangofield.LDObjectField(null=True)),
('decryption_proofs', helios.datatypes.djangofield.LDObjectField(null=True)),
- ('election', models.ForeignKey(to='helios.Election')),
+ ('election', models.ForeignKey(to='helios.Election', on_delete=models.CASCADE)),
],
options={
},
@@ -139,8 +139,8 @@ class Migration(migrations.Migration):
('vote', helios.datatypes.djangofield.LDObjectField(null=True)),
('vote_hash', models.CharField(max_length=100, null=True)),
('cast_at', models.DateTimeField(null=True)),
- ('election', models.ForeignKey(to='helios.Election')),
- ('user', models.ForeignKey(to='helios_auth.User', null=True)),
+ ('election', models.ForeignKey(to='helios.Election', on_delete=models.CASCADE)),
+ ('user', models.ForeignKey(to='helios_auth.User', null=True, on_delete=models.CASCADE)),
],
options={
},
@@ -156,7 +156,7 @@ class Migration(migrations.Migration):
('processing_started_at', models.DateTimeField(null=True)),
('processing_finished_at', models.DateTimeField(null=True)),
('num_voters', models.IntegerField(null=True)),
- ('election', models.ForeignKey(to='helios.Election')),
+ ('election', models.ForeignKey(to='helios.Election', on_delete=models.CASCADE)),
],
options={
},
@@ -173,13 +173,13 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='castvote',
name='voter',
- field=models.ForeignKey(to='helios.Voter'),
+ field=models.ForeignKey(to='helios.Voter', on_delete=models.CASCADE),
preserve_default=True,
),
migrations.AddField(
model_name='auditedballot',
name='election',
- field=models.ForeignKey(to='helios.Election'),
+ field=models.ForeignKey(to='helios.Election', on_delete=models.CASCADE),
preserve_default=True,
),
]
diff --git a/helios/migrations/0002_castvote_cast_ip.py b/helios/migrations/0002_castvote_cast_ip.py
index 47db5b169..bb7a42263 100644
--- a/helios/migrations/0002_castvote_cast_ip.py
+++ b/helios/migrations/0002_castvote_cast_ip.py
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
from django.db import models, migrations
diff --git a/helios/migrations/0003_auto_20160507_1948.py b/helios/migrations/0003_auto_20160507_1948.py
index 162d6bb54..8e6f65266 100644
--- a/helios/migrations/0003_auto_20160507_1948.py
+++ b/helios/migrations/0003_auto_20160507_1948.py
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
from django.db import models, migrations
diff --git a/helios/migrations/0004_auto_20170528_2025.py b/helios/migrations/0004_auto_20170528_2025.py
index d49bb6375..a437c5f11 100644
--- a/helios/migrations/0004_auto_20170528_2025.py
+++ b/helios/migrations/0004_auto_20170528_2025.py
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
from django.db import migrations, models
diff --git a/helios/migrations/0005_auto_20210123_0941.py b/helios/migrations/0005_auto_20210123_0941.py
new file mode 100644
index 000000000..355687c60
--- /dev/null
+++ b/helios/migrations/0005_auto_20210123_0941.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.28 on 2021-01-23 09:41
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('helios', '0004_auto_20170528_2025'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='election',
+ name='datatype',
+ field=models.CharField(default='legacy/Election', max_length=250),
+ ),
+ migrations.AlterField(
+ model_name='election',
+ name='election_type',
+ field=models.CharField(choices=[('election', 'Election'), ('referendum', 'Referendum')], default='election', max_length=250),
+ ),
+ migrations.AlterField(
+ model_name='voterfile',
+ name='voter_file',
+ field=models.FileField(max_length=250, null=True, upload_to='voters/%Y/%m/%d'),
+ ),
+ ]
diff --git a/helios/models.py b/helios/models.py
index 96b665418..95444b217 100644
--- a/helios/models.py
+++ b/helios/models.py
@@ -6,36 +6,34 @@
(ben@adida.net)
"""
-from django.db import models, transaction
-import json
-from django.conf import settings
-from django.core.mail import send_mail
+import copy
+import csv
+import datetime
+import uuid
-import datetime, logging, uuid, random, io
import bleach
-
-from crypto import electionalgs, algs, utils
-from helios import utils as heliosutils
-import helios.views
+from django.conf import settings
+from django.db import models, transaction
+from validate_email import validate_email
from helios import datatypes
-
-
+from helios import utils
+from helios.datatypes.djangofield import LDObjectField
# useful stuff in helios_auth
-from helios_auth.models import User, AUTH_SYSTEMS
from helios_auth.jsonfield import JSONField
-from helios.datatypes.djangofield import LDObjectField
+from helios_auth.models import User, AUTH_SYSTEMS
+from .crypto import algs
+from .crypto.elgamal import Cryptosystem
+from .crypto.utils import random, hash_b64
-import csv, copy
-import unicodecsv
class HeliosModel(models.Model, datatypes.LDObjectContainer):
class Meta:
abstract = True
class Election(HeliosModel):
- admin = models.ForeignKey(User)
-
+ admin = models.ForeignKey(User, on_delete=models.CASCADE)
+
uuid = models.CharField(max_length=50, null=False)
# keep track of the type and version of election, which will help dispatch to the right
@@ -44,10 +42,10 @@ class Election(HeliosModel):
# v3.1 will still use legacy/Election
# later versions, at some point will upgrade to "2011/01/Election"
datatype = models.CharField(max_length=250, null=False, default="legacy/Election")
-
+
short_name = models.CharField(max_length=100, unique=True)
name = models.CharField(max_length=250)
-
+
ELECTION_TYPES = (
('election', 'Election'),
('referendum', 'Referendum')
@@ -61,10 +59,10 @@ class Election(HeliosModel):
null=True)
private_key = LDObjectField(type_hint = 'legacy/EGSecretKey',
null=True)
-
+
questions = LDObjectField(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 = LDObjectField(type_hint = 'legacy/Eligibility',
@@ -74,10 +72,10 @@ class Election(HeliosModel):
# this is now used to indicate the state of registration,
# whether or not the election is frozen
openreg = models.BooleanField(default=False)
-
+
# featured election?
featured_p = models.BooleanField(default=False)
-
+
# voter aliases?
use_voter_aliases = models.BooleanField(default=False)
@@ -86,18 +84,18 @@ class Election(HeliosModel):
# randomize candidate order?
randomize_answer_order = models.BooleanField(default=False, null=False)
-
+
# where votes should be cast
cast_url = models.CharField(max_length = 500)
# dates at which this was touched
created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now_add=True)
-
+
# dates at which things happen for the election
frozen_at = models.DateTimeField(auto_now_add=False, default=None, null=True)
archived_at = models.DateTimeField(auto_now_add=False, default=None, null=True)
-
+
# dates for the election steps, as scheduled
# these are always UTC
registration_starts_at = models.DateTimeField(auto_now_add=False, default=None, null=True)
@@ -111,7 +109,7 @@ class Election(HeliosModel):
complaint_period_ends_at = models.DateTimeField(auto_now_add=False, default=None, null=True)
tallying_starts_at = models.DateTimeField(auto_now_add=False, default=None, null=True)
-
+
# dates when things were forced to be performed
voting_started_at = models.DateTimeField(auto_now_add=False, default=None, null=True)
voting_extended_until = models.DateTimeField(auto_now_add=False, default=None, null=True)
@@ -125,7 +123,7 @@ class Election(HeliosModel):
# the hash of all voters (stored for large numbers)
voters_hash = models.CharField(max_length=100, null=True)
-
+
# encrypted tally, each a JSON string
# used only for homomorphic tallies
encrypted_tally = LDObjectField(type_hint = 'legacy/Tally',
@@ -145,6 +143,9 @@ class Election(HeliosModel):
# downloadable election info
election_info_url = models.CharField(max_length=300, null=True)
+ class Meta:
+ app_label = 'helios'
+
# metadata for the election
@property
def metadata(self):
@@ -179,28 +180,32 @@ def last_alias_num(self):
"""
if not self.use_voter_aliases:
return None
-
- return heliosutils.one_val_raw_sql("select max(cast(substring(alias, 2) as integer)) from " + Voter._meta.db_table + " where election_id = %s", [self.id]) or 0
+
+ return utils.one_val_raw_sql("select max(cast(substr(alias, 2) as integer)) from " + Voter._meta.db_table + " where election_id = %s", [self.id]) or 0
@property
def encrypted_tally_hash(self):
if not self.encrypted_tally:
return None
- return utils.hash_b64(self.encrypted_tally.toJSON())
+ return hash_b64(self.encrypted_tally.toJSON())
@property
def is_archived(self):
- return self.archived_at != None
+ return self.archived_at is not None
@property
def description_bleached(self):
- return bleach.clean(self.description, tags = bleach.ALLOWED_TAGS + ['p', 'h4', 'h5', 'h3', 'h2', 'br', 'u'])
+ return bleach.clean(self.description,
+ tags=list(bleach.ALLOWED_TAGS) + ['p', 'h4', 'h5', 'h3', 'h2', 'br', 'u'],
+ strip=True,
+ strip_comments=True,
+ )
@classmethod
def get_featured(cls):
return cls.objects.filter(featured_p = True).order_by('short_name')
-
+
@classmethod
def get_or_create(cls, **kwargs):
return cls.objects.get_or_create(short_name = kwargs['short_name'], defaults=kwargs)
@@ -208,43 +213,43 @@ def get_or_create(cls, **kwargs):
@classmethod
def get_by_user_as_admin(cls, user, archived_p=None, limit=None):
query = cls.objects.filter(admin = user)
- if archived_p == True:
+ if archived_p is True:
query = query.exclude(archived_at= None)
- if archived_p == False:
+ if archived_p is False:
query = query.filter(archived_at= None)
query = query.order_by('-created_at')
if limit:
return query[:limit]
else:
return query
-
+
@classmethod
def get_by_user_as_voter(cls, user, archived_p=None, limit=None):
query = cls.objects.filter(voter__user = user)
- if archived_p == True:
+ if archived_p is True:
query = query.exclude(archived_at= None)
- if archived_p == False:
+ if archived_p is False:
query = query.filter(archived_at= None)
query = query.order_by('-created_at')
if limit:
return query[:limit]
else:
return query
-
+
@classmethod
def get_by_uuid(cls, uuid):
try:
return cls.objects.select_related().get(uuid=uuid)
except cls.DoesNotExist:
return None
-
+
@classmethod
def get_by_short_name(cls, short_name):
try:
return cls.objects.get(short_name=short_name)
except cls.DoesNotExist:
return None
-
+
def save_questions_safely(self, questions):
"""
Because Django doesn't let us override properties in a Pythonic way... doing the brute-force thing.
@@ -254,11 +259,11 @@ def save_questions_safely(self, questions):
for answer_url in q['answer_urls']:
if not answer_url or answer_url == "":
continue
-
+
# abort saving if bad URL
if not (answer_url[:7] == "http://" or answer_url[:8]== "https://"):
return False
-
+
self.questions = questions
return True
@@ -266,16 +271,20 @@ def add_voters_file(self, uploaded_file):
"""
expects a django uploaded_file data structure, which has filename, content, size...
"""
- # now we're just storing the content
- # random_filename = str(uuid.uuid4())
- # new_voter_file.voter_file.save(random_filename, uploaded_file)
+ voter_file_content_bytes = uploaded_file.read()
+
+ # usually it's utf-8 encoded, but occasionally it's latin-1
+ try:
+ voter_file_content = voter_file_content_bytes.decode('utf-8')
+ except:
+ voter_file_content = voter_file_content_bytes.decode('latin-1')
- new_voter_file = VoterFile(election = self, voter_file_content = uploaded_file.read())
+ new_voter_file = VoterFile(election = self, voter_file_content = voter_file_content)
new_voter_file.save()
-
+
self.append_log(ElectionLog.VOTER_FILE_ADDED)
return new_voter_file
-
+
def user_eligible_p(self, user):
"""
Checks if a user is eligible for this election.
@@ -283,15 +292,15 @@ def user_eligible_p(self, user):
# registration closed, then eligibility doesn't come into play
if not self.openreg:
return False
-
- if self.eligibility == None:
+
+ if self.eligibility is None:
return True
-
+
# is the user eligible for one of these cases?
for eligibility_case in self.eligibility:
if user.is_eligible_for(eligibility_case):
return True
-
+
return False
def eligibility_constraint_for(self, user_type):
@@ -299,7 +308,7 @@ def eligibility_constraint_for(self, user_type):
return []
# constraints that are relevant
- relevant_constraints = [constraint['constraint'] for constraint in self.eligibility if constraint['auth_system'] == user_type and constraint.has_key('constraint')]
+ relevant_constraints = [constraint['constraint'] for constraint in self.eligibility if constraint['auth_system'] == user_type and 'constraint' in constraint]
if len(relevant_constraints) > 0:
return relevant_constraints[0]
else:
@@ -309,23 +318,23 @@ def eligibility_category_id(self, user_type):
"when eligibility is by category, this returns the category_id"
if not self.eligibility:
return None
-
+
constraint_for = self.eligibility_constraint_for(user_type)
if len(constraint_for) > 0:
constraint = constraint_for[0]
return AUTH_SYSTEMS[user_type].eligibility_category_id(constraint)
else:
return None
-
+
@property
def pretty_eligibility(self):
if not self.eligibility:
return "Anyone can vote."
else:
return_val = "
"
-
+
for constraint in self.eligibility:
- if constraint.has_key('constraint'):
+ if 'constraint' in constraint:
for one_constraint in constraint['constraint']:
return_val += "
-
+ If you cancel now, your ballot will NOT be recorded.
You can start the voting process over again, of course.
diff --git a/helios/templates/_castconfirm_password.html b/helios/templates/_castconfirm_password.html
index 25e31de21..ebfb8e0f5 100644
--- a/helios/templates/_castconfirm_password.html
+++ b/helios/templates/_castconfirm_password.html
@@ -1,23 +1,28 @@
-Please provide the voter ID and password you received by email.