2017-09-22 15:59:15 +10:00
# Eos - Verifiable elections
# Copyright © 2017 RunasSudo (Yingtong Li)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from eos . core . objects import *
2017-12-11 13:00:41 +11:00
from eos . core . bigint import *
2017-09-22 15:59:15 +10:00
from eos . base . workflow import *
2017-09-24 01:23:43 +10:00
class Answer ( EmbeddedObject ) :
2017-09-22 15:59:15 +10:00
pass
2017-09-24 01:23:43 +10:00
class EncryptedAnswer ( EmbeddedObject ) :
pass
class NullEncryptedAnswer ( EncryptedAnswer ) :
answer = EmbeddedObjectField ( )
2017-09-24 13:32:46 +10:00
def decrypt ( self ) :
2017-11-29 18:29:42 +11:00
return None , self . answer
2017-09-22 15:59:15 +10:00
class Ballot ( EmbeddedObject ) :
2017-11-23 23:10:57 +11:00
#_id = UUIDField()
2017-09-24 01:23:43 +10:00
encrypted_answers = EmbeddedObjectListField ( )
2017-11-24 20:26:18 +11:00
election_id = UUIDField ( )
election_hash = StringField ( )
2017-11-28 22:43:32 +11:00
2017-12-07 18:32:01 +11:00
answers = EmbeddedObjectListField ( is_hashed = False ) # Used for ballots to be audited
2017-11-28 22:43:32 +11:00
def deaudit ( self ) :
encrypted_answers_deaudit = EosList ( )
2017-12-04 13:51:45 +11:00
for encrypted_answer in self . encrypted_answers :
encrypted_answers_deaudit . append ( encrypted_answer . deaudit ( ) )
2017-11-28 22:43:32 +11:00
return Ballot ( encrypted_answers = encrypted_answers_deaudit , election_id = self . election_id , election_hash = self . election_hash )
2017-09-22 15:59:15 +10:00
2017-11-23 23:10:57 +11:00
class Vote ( EmbeddedObject ) :
2017-12-11 13:53:25 +11:00
_ver = StringField ( default = ' 0.4 ' )
2017-11-23 23:10:57 +11:00
ballot = EmbeddedObjectField ( )
2017-11-24 20:26:18 +11:00
cast_at = DateTimeField ( )
2017-12-11 13:53:25 +11:00
cast_ip = StringField ( is_protected = True )
cast_fingerprint = BlobField ( is_protected = True )
2017-11-23 23:10:57 +11:00
2017-09-22 15:59:15 +10:00
class Voter ( EmbeddedObject ) :
_id = UUIDField ( )
2017-11-23 23:10:57 +11:00
votes = EmbeddedObjectListField ( )
2017-09-22 15:59:15 +10:00
2017-11-26 11:17:28 +11:00
class User ( EmbeddedObject ) :
2017-12-07 15:33:11 +11:00
admins = [ ]
def matched_by ( self , other ) :
return self == other
def is_admin ( self ) :
for admin in User . admins :
if admin . matched_by ( self ) :
return True
return False
2017-11-26 11:17:28 +11:00
2017-11-26 20:48:15 +11:00
def generate_password ( ) :
if is_python :
#__pragma__('skip')
import random
return ' ' . join ( random . SystemRandom ( ) . choices ( ' 23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz ' , k = 12 ) )
#__pragma__('noskip')
else :
return None
class EmailUser ( User ) :
name = StringField ( )
email = StringField ( is_protected = True )
password = StringField ( is_protected = True , default = generate_password )
def matched_by ( self , other ) :
if not isinstance ( other , EmailUser ) :
return False
2017-11-28 00:26:13 +11:00
return self . email . lower ( ) == other . email . lower ( ) and self . password == other . password
2017-11-26 20:48:15 +11:00
def send_email ( self , host , port , username , password , from_email , content ) :
#__pragma__('skip')
import smtplib
#__pragma__('noskip')
with smtplib . SMTP ( host , port ) as smtp :
if username is not None :
smtp . login ( username , password )
smtp . sendmail ( from_email , [ self . email ] , content )
def email_password ( self , host , port , username , password , from_email ) :
self . send_email ( host , port , username , password , from_email , ' Subject: Registered to vote in {1} \n From: {4} \n To: {2} \n \n Dear {0} , \n \n You are registered to vote in the election {1} . Your log in details are as follows: \n \n Email: {2} \n Password: {3} ' . format ( self . name , self . recurse_parents ( Election ) . name , self . email , self . password , from_email ) )
2017-11-26 11:17:28 +11:00
class UserVoter ( Voter ) :
user = EmbeddedObjectField ( )
@property
def name ( self ) :
return self . user . name
2017-11-23 18:18:01 +11:00
2017-09-22 15:59:15 +10:00
class Question ( EmbeddedObject ) :
prompt = StringField ( )
2017-09-24 13:25:16 +10:00
class Result ( EmbeddedObject ) :
pass
2017-09-22 15:59:15 +10:00
2017-12-07 16:04:24 +11:00
class ListChoiceQuestion ( Question ) :
2017-12-11 13:00:41 +11:00
_ver = StringField ( default = ' 0.5 ' )
2017-12-11 11:25:01 +11:00
choices = EmbeddedObjectListField ( )
2017-11-23 21:07:16 +11:00
min_choices = IntField ( )
max_choices = IntField ( )
2017-12-11 13:00:41 +11:00
randomise_choices = BooleanField ( default = False )
2017-11-24 19:51:10 +11:00
def pretty_answer ( self , answer ) :
2017-12-07 16:04:24 +11:00
if len ( answer . choices ) == 0 :
return ' (blank votes) '
2017-12-11 12:22:22 +11:00
flat_choices = self . flatten_choices ( )
return ' , ' . join ( [ flat_choices [ choice ] . name for choice in answer . choices ] )
2017-12-07 18:32:01 +11:00
def max_bits ( self ) :
answer = self . answer_type ( choices = list ( range ( len ( self . choices ) ) ) )
return len ( EosObject . to_json ( EosObject . serialise_and_wrap ( answer ) ) ) * 8
2017-12-11 12:22:22 +11:00
def flatten_choices ( self ) :
# Return a flat list of Choices, without Tickets
flat_choices = [ ]
for choice in self . choices :
if isinstance ( choice , Ticket ) :
for choice2 in choice . choices :
flat_choices . append ( choice2 )
else :
flat_choices . append ( choice )
return flat_choices
2017-12-11 13:00:41 +11:00
def randomised_choices ( self ) :
if not self . randomise_choices :
return self . choices
else :
# Clone list
output = EosList ( [ x for x in self . choices ] )
# Fisher-Yates shuffle
i = len ( output )
while i != 0 :
rnd = BigInt . noncrypto_random ( 0 , i - 1 )
rnd = rnd . __int__ ( )
i - = 1
output [ rnd ] , output [ i ] = output [ i ] , output [ rnd ]
return output
2017-12-07 16:04:24 +11:00
2017-09-24 01:23:43 +10:00
class ApprovalAnswer ( Answer ) :
choices = ListField ( IntField ( ) )
2017-12-07 18:32:01 +11:00
class ApprovalQuestion ( ListChoiceQuestion ) :
answer_type = ApprovalAnswer
2017-11-27 22:56:43 +11:00
class PreferentialAnswer ( Answer ) :
choices = ListField ( IntField ( ) )
2017-12-07 18:32:01 +11:00
class PreferentialQuestion ( ListChoiceQuestion ) :
answer_type = PreferentialAnswer
2017-12-11 11:25:01 +11:00
class Choice ( EmbeddedObject ) :
name = StringField ( )
party = StringField ( default = None )
2017-12-11 12:22:22 +11:00
@property
def party_or_ticket ( self ) :
if self . party is not None :
return self . party
else :
ticket = self . recurse_parents ( Ticket )
if ticket :
return ticket . name
return None
2017-12-11 11:25:01 +11:00
class Ticket ( EmbeddedObject ) :
name = StringField ( )
choices = EmbeddedObjectListField ( )
2017-09-28 16:46:30 +10:00
class RawResult ( Result ) :
2017-11-29 18:29:42 +11:00
plaintexts = ListField ( EmbeddedObjectListField ( ) )
2017-09-28 16:46:30 +10:00
answers = EmbeddedObjectListField ( )
2017-11-24 19:51:10 +11:00
def count ( self ) :
combined = [ ]
for answer in self . answers :
index = next ( ( i for i , val in enumerate ( combined ) if val [ 0 ] == answer ) , None )
if index is None :
combined . append ( [ answer , 1 ] )
else :
combined [ index ] [ 1 ] + = 1
combined . sort ( key = lambda x : x [ 1 ] , reverse = True )
return combined
2017-09-24 13:25:16 +10:00
2017-09-22 15:59:15 +10:00
class Election ( TopLevelObject ) :
_id = UUIDField ( )
2017-09-22 16:57:40 +10:00
workflow = EmbeddedObjectField ( Workflow ) # Once saved, we don't care what kind of workflow it is
2017-09-22 15:59:15 +10:00
name = StringField ( )
2017-12-04 14:30:41 +11:00
kind = StringField ( default = ' election ' )
2017-11-23 20:42:22 +11:00
voters = EmbeddedObjectListField ( is_hashed = False )
2017-09-22 16:57:40 +10:00
questions = EmbeddedObjectListField ( )
2017-11-23 20:42:22 +11:00
results = EmbeddedObjectListField ( is_hashed = False )
2017-11-29 18:29:42 +11:00
def verify ( self ) :
#__pragma__('skip')
from eos . core . hashing import SHA256
#__pragma__('noskip')
election_hash = SHA256 ( ) . update_obj ( self ) . hash_as_b64 ( )
for voter in self . voters :
for vote in voter . votes :
if vote . ballot . election_id != self . _id :
raise Exception ( ' Invalid election ID on ballot ' )
if vote . ballot . election_hash != election_hash :
raise Exception ( ' Invalid election hash on ballot ' )