# pre_election_audit.py
# Ronald L. Rivest
# October 11, 2009
#
# This Python program is for use with the Scantegrity II election system.
# See www.scantegrity.org for information on Scantegrity II.
#
# It was written specifically for the version of Scantegrity II used
# in the Takoma Park, Maryland election of November 3rd, 2009. It
# may need slightly modifications for use with other versions of
# Scantegrity.
#
# This program is invoked as:
# python pre_election_audit.py randomDataFileName inFileName outFileName percentage_challenged
# randDataFileName must be given; it is arbitrary file name (e.g. stock price data)
# if inFileName is omitted, it is MeetingOneIn.xml
# if outFileName is omitted, it is MeetingTwoIn.xml
# percentage_challenged should be an integer between 0 and 100, inclusive
# if percentage_challenged is omitted, it is 50 (i.e. 50% of ballots will be audited)
#
# The randomDataFile may contain arbitrary data; in practice it
# is expected to contain closing stock price data, as produced by
# get_latest_djia_stock_prices.py
# which produces the file
# djia_stock_prices_latest.txt
#
# This program:
#
# (1) Reads an XML parameter specification file named
# MeetingOneIn.xml (or something else if specified on command line)
# which will look something like:
#
#
# D:\PunchScan2.0\PunchScan2.0\Elections\TP Nov 3 2009, mock\ward2\ElectionSpec.xml
# 100
# 1
# d2FyZDIgICAgICAgICAgIA==
# D:\PunchScan2.0\PunchScan2.0\Elections\TP Nov 3 2009, mock\ward2\partitions.xml
#
#
# (2) Obtains the following parameters from that file:
# noBallots (nonnegative integer)
# constant (base-64 encoded election constant)
#
# (3) Produces an output file called
# MeetingTwoIn.xml (or something else if specified on command line)
# containing
#
#
#
#
# ROWDATA
#
#
#
#
# where ROWDATA is a number of distinct rows of the form:
#
# where each row id is between 0 and noBallots-1
# The number of rows is equal to the specified number of challenges.
#
# IMPORTS
#
import base64
import hashlib
import sys
from xml.etree import ElementTree as etree
#
# ROUTINE FOR READING XML INPUT FILE
#
def read_MeetingOneIn(inFileName):
"""
Read the XML input file inFileName, and
return the values of the noBallots and constant found therein.
"""
doc = etree.parse(inFileName)
root = doc.getroot()
for node in root:
if node.tag == "noBallots":
noBallots = int(node.text)
if node.tag == "constant":
constant = base64.decodestring(node.text);
return (noBallots,constant)
#
# ROUTINE FOR PRODUCING OUTPUT FILE
#
def write_MeetingTwoIn(outFileName,challenge_list):
file = open(outFileName,"w")
file.write("\n")
for row in challenge_list:
file.write('
\n'%row)
file.write("\n")
file.close()
def prn(seed,index,modulus):
"""
Return pseudo-random number based on seed, index, and modulus:
seed is arbitrary byte string
index is integer to distinguish different calls
modulus specifies what the output modulus is; value is between 0 and modulus-1
"""
# Append decimal representation of index at end of seed to form hash input
hash_input = seed + "%d"%index
# Apply SHA1 hash function to get a byte sequence of length 20 bytes (160 bits),
# encoded as 40 hex digits.
hash_output = hashlib.sha1(hash_input).hexdigest()
# Return hash output (treated as integer) modulo given modulus
hash_value = int(hash_output,16) # interpret hash output as a base-16 integer
output_value = hash_value % modulus # mod modulus ==> output value
return output_value
def main():
args = sys.argv[1:]
print
print " Scantegrity II Pre-Election Audit Challenge Generator"
print " Version 1.0. October 11, 2009."
print
if len(args) == 0:
print ' usage: python pre_election_audit.py randomDataFileName inFileName outFileName percentage_challenged'
print " randDataFileName must be given; it is arbitrary file name (e.g. stock price data)"
print " if inFileName is omitted, it is MeetingOneIn.xml"
print " if outFileName is omitted, it is MeetingTwoIn.xml"
print " percentage_challenged should be between 0 and 100, inclusive"
print " if percentage_challenged is omitted, it is 50 (i.e. 50% of ballots will be audited)"
print
sys.exit(-1)
randomDataFileName = args[0]
if len(args) >= 2:
inFileName = args[1]
else:
inFileName = "MeetingOneIn.xml"
if len(args) >= 3:
outFileName = args[2]
else:
outFileName = "MeetingTwoIn.xml"
if len(args) >= 4:
percentage_challenged = int(args[3])
percentage_challenged = min(100,max(0,percentage_challenged))
else:
percentage_challenged = 50
# read random data seed from file
print " Step 1. Obtain random data seed from:",randomDataFileName
f = open(randomDataFileName,"rb")
seed = f.read()
f.close()
# Read XML input (MeetingOneIn.xml file by default)
(noBallots,constant) = read_MeetingOneIn(inFileName)
print " Step 2. Read input file:", inFileName
print " Number of ballots = ",noBallots
print " constant = '",constant,"'"
# set noChallenges
noChallenges = (percentage_challenged * noBallots) // 100
noChallenges = min(noBallots,noChallenges)
print " Step 3. Determine number of challenges desired = ",noChallenges
# append election constant to seed
seed = seed + constant
# make up list of challenges
print " Step 4. Generate challenges."
challenge_list = []
challenge_array = [0]*noBallots # to mark ones already taken
counter = 0 # counts number of prn calls
for i in range(noChallenges): # until we have desired number challenges
while (challenge_array[prn(seed,counter,noBallots)]==1):
counter += 1
index = prn(seed,counter,noBallots) # mark this row index as now used
challenge_array[index] = 1
challenge_list.append(index) # and save it on list of challenges
challenge_list = sorted(challenge_list) # sort into order (prettier output)
print " Number of challenges produced =", len(challenge_list)
# save output in desired format
print " Step 5. Write challenge list to output file:",outFileName
print
write_MeetingTwoIn(outFileName,challenge_list)
print " Done."
print
if __name__ == '__main__':
main()