com.vvote.verifier.component.ballotGen.BallotGenerationVerifier.java Source code

Java tutorial

Introduction

Here is the source code for com.vvote.verifier.component.ballotGen.BallotGenerationVerifier.java

Source

/**
 * This file is part of vVoteVerifier which is designed to be used as a verifiation tool for the vVote Election System.
 * Copyright (C) 2014  James Rumble (jerumble@gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.vvote.verifier.component.ballotGen;

import java.io.File;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.bouncycastle.cert.CertException;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.prng.FixedSecureRandom;
import org.bouncycastle.crypto.prng.SP800SecureRandom;
import org.bouncycastle.crypto.prng.SP800SecureRandomBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.vvote.CryptoConstants;
import com.vvote.JSONConstants;
import com.vvote.commits.CommitIdentifier;
import com.vvote.datafiles.commits.auditcommit.BallotAuditCommit;
import com.vvote.datafiles.commits.auditcommit.BallotGenerationRandomness;
import com.vvote.datafiles.commits.auditcommit.OpenedRandomnessCommitments;
import com.vvote.datafiles.commits.auditcommit.RandomnessPair;
import com.vvote.datafiles.commits.auditcommit.WBBSignature;
import com.vvote.datafiles.commits.gencommit.CommittedBallot;
import com.vvote.datafiles.commits.mixrandomcommit.MixCommitData;
import com.vvote.datafiles.commits.mixrandomcommit.MixRandomCommit;
import com.vvote.datafiles.commits.mixrandomcommit.RandomnessServerCommits;
import com.vvote.datafiles.wbb.CertificatesFile;
import com.vvote.ec.ElGamalECPoint;
import com.vvote.ec.ElGamalECPointComparator;
import com.vvote.ec.IndexedElGamalECPoint;
import com.vvote.thirdparty.json.orgjson.JSONObject;
import com.vvote.verifier.component.ComponentVerifier;
import com.vvote.verifier.exceptions.ComponentDataStoreException;
import com.vvote.verifier.exceptions.ComponentSpecException;
import com.vvote.verifier.exceptions.ComponentVerifierException;
import com.vvote.verifier.exceptions.DataStoreException;
import com.vvote.verifier.exceptions.SpecException;
import com.vvote.verifier.exceptions.VerifierException;
import com.vvote.verifierlibrary.exceptions.BLSSignatureException;
import com.vvote.verifierlibrary.exceptions.CommitException;
import com.vvote.verifierlibrary.exceptions.FileHashException;
import com.vvote.verifierlibrary.utils.Utils;
import com.vvote.verifierlibrary.utils.comparators.BallotSerialNumberComparator;
import com.vvote.verifierlibrary.utils.crypto.CryptoUtils;
import com.vvote.verifierlibrary.utils.crypto.ECUtils;
import com.vvote.verifierlibrary.utils.crypto.bls.BLSCombiner;

/**
 * BallotGenerationVerifier is used for carrying out validation and verification
 * of the ballot generation process carried out by each client.
 * BallotGenerationVerifier is important to ensure that each PoD Printer has
 * performed honestly. PoD Printers may influence the produced generic ballot
 * ciphers and must therefore be audited to ensure they have acted honestly.
 * 
 * The Ballot Generation confirmation checking process provides an assumption
 * that each PoD Printer has acted honestly by auditing a suitably large number
 * of randomly and unpredictably chosen ballots, which if shown to be correct,
 * provide confidence in the accuracy of those generic ballots not checked which
 * can then be used for voting purposes with some kind of an assurance that they
 * have been reliably and honestly produced by each of the audited PoD Printers.
 * 
 * @author James Rumble
 * 
 */
public class BallotGenerationVerifier extends ComponentVerifier {

    /**
     * provides logging for the class
     */
    private static final Logger logger = LoggerFactory.getLogger(BallotGenerationVerifier.class);

    /**
     * Provides logging for the actual results produced in the verifier
     */
    private static final Logger resultsLogger = LoggerFactory.getLogger("results");

    /**
     * Create a SHA 256 message digest array of a specified size
     * 
     * @param size
     * @return an array list of initialised message digests
     */
    private static ArrayList<MessageDigest> createMessageDigestArray(int size) {
        ArrayList<MessageDigest> digestArray = new ArrayList<MessageDigest>();

        for (int i = 0; i < size; i++) {
            try {
                digestArray.add(MessageDigest.getInstance("SHA-256"));
            } catch (NoSuchAlgorithmException e) {
                logger.error("No such algorithm: {}", e);
                return null;
            }
        }

        return digestArray;
    }

    /**
     * Gets the permutation for a list of IndexedElGamalECPoint objects. Gets a
     * permutation to represent the candidate id ordering after the
     * re-encryptions and sorting has taken place
     * 
     * @param combinedBallotCiphers
     * @return the permutation constructed
     */
    private static String getPermutation(List<IndexedElGamalECPoint> combinedBallotCiphers) {
        StringBuilder builder = new StringBuilder();

        boolean isFirst = true;
        for (IndexedElGamalECPoint currentPoint : combinedBallotCiphers) {
            if (!isFirst) {
                builder.append(JSONConstants.PREFERENCE_SEPARATOR);
            } else {
                isFirst = false;
            }
            builder.append(currentPoint.getIndex());
        }
        builder.append(JSONConstants.RACE_SEPARATOR);
        return builder.toString();
    }

    /**
     * Holds the combined randomness values. Holds a map of serialNo : a list of
     * combined randomness values
     */
    private Map<String, List<MessageDigest>> combinedRandomness = null;

    /**
     * Constructor for a ballot generation verifier component
     * 
     * @param dataStore
     * @param spec
     * @throws ComponentVerifierException
     * @throws VerifierException
     */
    public BallotGenerationVerifier(BallotGenDataStore dataStore, BallotGenerationVerifierSpec spec)
            throws ComponentVerifierException, VerifierException {
        super(dataStore, spec);

        logger.info("Setting up the Ballot Generation Verifier");

        CryptoUtils.initProvider();

        ECUtils.changeCurve(CryptoConstants.BallotGenerationVerifier.CURVE_NAME);

        this.combinedRandomness = new HashMap<String, List<MessageDigest>>();
    }

    /**
     * Constructor for a BallotGenerationVerifier from JSONObject
     * representations of the spec objects
     * 
     * @param dataStore
     * @param spec
     * @throws ComponentSpecException
     * @throws ComponentVerifierException
     * @throws VerifierException
     * @throws SpecException
     */
    public BallotGenerationVerifier(BallotGenDataStore dataStore, JSONObject spec)
            throws ComponentVerifierException, ComponentSpecException, VerifierException, SpecException {
        this(dataStore, new BallotGenerationVerifierSpec(spec));
    }

    /**
     * Constructor for a BallotGenerationVerifier from string representations of
     * the spec objects
     * 
     * @param dataStore
     * 
     * @param spec
     * @throws ComponentSpecException
     * @throws ComponentVerifierException
     * @throws VerifierException
     * @throws SpecException
     */
    public BallotGenerationVerifier(BallotGenDataStore dataStore, String spec)
            throws ComponentVerifierException, ComponentSpecException, VerifierException, SpecException {
        this(dataStore, new BallotGenerationVerifierSpec(spec));
    }

    /**
     * Constructor for a BallotGenerationVerifier from string representations of
     * the spec objects
     * 
     * @param spec
     * @param basePath
     * @param useExtraCommits
     * @throws ComponentSpecException
     * @throws ComponentVerifierException
     * @throws ComponentDataStoreException
     * @throws VerifierException
     * @throws SpecException
     * @throws DataStoreException
     */
    public BallotGenerationVerifier(String spec, String basePath, boolean useExtraCommits)
            throws ComponentVerifierException, ComponentSpecException, ComponentDataStoreException,
            VerifierException, DataStoreException, SpecException {
        this(new BallotGenDataStore(new BallotGenerationVerifierSpec(spec), basePath, useExtraCommits),
                new BallotGenerationVerifierSpec(spec));
    }

    /**
     * Constructor for a BallotGenerationVerifier from JSONObject
     * representations of the spec objects
     * 
     * @param spec
     * @param basePath
     * @param useExtraCommits
     * @throws ComponentSpecException
     * @throws ComponentVerifierException
     * @throws ComponentDataStoreException
     * @throws VerifierException
     * @throws SpecException
     * @throws DataStoreException
     */
    public BallotGenerationVerifier(JSONObject spec, String basePath, boolean useExtraCommits)
            throws ComponentVerifierException, ComponentSpecException, ComponentDataStoreException,
            VerifierException, DataStoreException, SpecException {
        this(new BallotGenDataStore(spec, basePath, useExtraCommits), new BallotGenerationVerifierSpec(spec));
    }

    /**
     * Combine the randomness values received by each of the PoD Printers from
     * each of the mix servers resulting in a single randomness value produced
     * from the combination of the randomness values from each of the mix
     * servers
     * 
     * @return true if the combined randomness values are calculated correctly
     */
    public boolean combineRandomnessValues() {

        logger.debug("Combining randomness values");

        this.combinedRandomness = new HashMap<String, List<MessageDigest>>();

        BallotAuditCommit auditCommit = null;

        BallotGenerationRandomness currentBallotRandomness = null;

        // loop over each printer to audit
        for (CommitIdentifier identifier : this.getDataStore().getAuditData().keySet()) {
            auditCommit = this.getDataStore().getAuditData().get(identifier);

            // loop over the ballots to audit
            for (String serialNumber : auditCommit.getRandomnessCommitmentSerialNumbers()) {

                currentBallotRandomness = auditCommit.getRandomnessCommit(serialNumber);

                if (!this.combineRandomnessValues(currentBallotRandomness, identifier)) {
                    return false;
                }

                auditCommit.freeRandomnessCommit(serialNumber);
            }
        }

        logger.debug("Successfully combined randomness values");
        resultsLogger.info("Successfully combined randomness values");

        return true;
    }

    /**
     * Carries out the specific combination of randomness values for the ballot
     * specified using the serial number. Combines the randomness values
     * received to construct the specific ballot from each of the mix servers
     * resulting in a single randomness value which was actually used to
     * construct the ballot itself
     * 
     * @param currentBallotRandomness
     * 
     * @param identifier
     * @return true if the combined randomness values are calculated correctly
     */
    public boolean combineRandomnessValues(BallotGenerationRandomness currentBallotRandomness,
            CommitIdentifier identifier) {
        int randomnessValues = this.getDataStore().getNumberOfRandomnessValuesExpected();
        String currentRandomnessValue = null;

        final String serialNo = currentBallotRandomness.getSerialNo();

        logger.debug("Combining randomness values for client: '{}', serialNo: '{}'", identifier, serialNo);

        // produce message digest array/storage for combined
        // randomness values for each ballot to be audited
        this.getCombinedRandomness().put(serialNo, createMessageDigestArray(randomnessValues));

        // loop over each randomness value - which represents each candidate
        // id
        for (int i = 0; i < randomnessValues; i++) {

            // for each randomness value received for the current candidate
            // id
            for (OpenedRandomnessCommitments podOpenedRandomness : currentBallotRandomness
                    .getOpenedRandomnessValues()) {

                // get the current randomness value (used for encryption)
                // from the current mix server
                currentRandomnessValue = podOpenedRandomness.getRandomnessPair(i).getRandomnessValue();

                logger.debug(
                        "Combining randomness values for client: '{}', serialNo: '{}' - randomness value: '{}'",
                        identifier, serialNo, currentRandomnessValue);

                // update the randomness value for the correct candidate id
                // at the correct serial number
                this.getCombinedRandomness().get(serialNo).get(i)
                        .update(Utils.decodeHexData(currentRandomnessValue));
            }
        }

        logger.debug("Successfully combined randomness values for client: '{}', serialNo: '{}'", identifier,
                serialNo);

        return true;
    }

    /**
     * Combines the randomness values for a specific ballot
     * 
     * @param serialNumber
     * @return true if the randomness values are combined successfully
     */
    public boolean combineRandomnessValues(String serialNumber) {

        if (!this.isAuditBallot(serialNumber)) {
            logger.info(
                    "Ballot specified with serial number: {} was not chosen for auditing and it is therefore not possible to verify this ballot",
                    serialNumber);
            return false;
        }

        BallotAuditCommit auditCommit = null;

        BallotGenerationRandomness currentBallotRandomness = null;

        // loop over each printer to audit
        for (CommitIdentifier identifier : this.getDataStore().getAuditData().keySet()) {
            auditCommit = this.getDataStore().getAuditData().get(identifier);

            if (auditCommit.getRandomnessCommitmentSerialNumbers().contains(serialNumber)) {
                currentBallotRandomness = auditCommit.getRandomnessCommit(serialNumber);

                if (this.combineRandomnessValues(currentBallotRandomness, identifier)) {
                    return true;
                }

                auditCommit.freeRandomnessCommit(serialNumber);
            }
        }

        return false;
    }

    @Override
    public boolean doVerification() {

        logger.info("Starting Ballot Generation Verification");
        resultsLogger.info("Starting Ballot Generation verification");

        boolean verified = super.doVerification();

        try {
            if (!this.verifyNumberOfRandomnessValuesReceivedByPODPrinters()) {
                verified = false;
            }

            if (!this.verifyNumberOfBallotsToAudit()) {
                verified = false;
            }

            if (!this.verifyFiatShamirCalculation()) {
                verified = false;
            }

            BallotAuditCommit auditCommit = null;

            BallotGenerationRandomness currentBallotRandomness = null;

            logger.info("Starting the verification of each ballot chosen for Ballot Generation Auditing");

            // loop over each printer to audit
            for (CommitIdentifier identifier : this.getDataStore().getAuditData().keySet()) {
                auditCommit = this.getDataStore().getAuditData().get(identifier);

                logger.debug(
                        "Starting the verification of Public WBB commitment with identifier: {} for outer attachment file: {}, inner attachment file: {}",
                        identifier, auditCommit.getAttachmentFilePath(), auditCommit.getMessage().getFileName());
                resultsLogger.info(
                        "Starting the verification of Public WBB commitment with identifier: {} for outer attachment file: {}, inner attachment file: {}",
                        identifier, auditCommit.getAttachmentFilePath(), auditCommit.getMessage().getFileName());

                // loop over the ballots to audit
                for (String serialNumber : auditCommit.getRandomnessCommitmentSerialNumbers()) {

                    currentBallotRandomness = auditCommit.getRandomnessCommit(serialNumber);

                    if (!this.verifyRandomness(currentBallotRandomness, identifier)) {
                        verified = false;
                    }

                    if (!this.combineRandomnessValues(currentBallotRandomness, identifier)) {
                        verified = false;
                    }

                    if (!this.verifyEncryptions(currentBallotRandomness, identifier)) {
                        verified = false;
                    }

                    auditCommit.freeRandomnessCommit(serialNumber);
                }
            }
        } catch (CommitException e) {
            logger.error("Unable to continue verification.", e);
            resultsLogger.error("Unable to continue verification.", e);
            return false;
        }

        if (verified) {
            logger.debug("Ballot Generation Verification was carried out successfully");
            resultsLogger.info("Ballot Generation Verification was carried out successfully");
        } else {
            logger.debug(
                    "Ballot Generation Verification was not carried out successfully. The data provided needs to be checked in addition to the logs");
            resultsLogger.info(
                    "Ballot Generation Verification was not carried out successfully. The data provided needs to be checked in addition to the logs");
        }

        return verified;
    }

    /**
     * Carries out the verification for a single ballot with the provided serial
     * number
     * 
     * @param serialNumber
     * @return true if the verification was successful for the ballot with the
     *         provided serial number
     */
    public boolean doVerification(String serialNumber) {

        logger.info("Starting Ballot Generation Verification for ballot with serial number: {}", serialNumber);

        if (!this.isAuditBallot(serialNumber)) {
            logger.error(
                    "Ballot specified with serial number: {} was not chosen for auditing and it is therefore not possible to verify this ballot",
                    serialNumber);
            resultsLogger.error(
                    "Ballot specified with serial number: {} was not chosen for auditing and it is therefore not possible to verify this ballot",
                    serialNumber);
            return false;
        }

        boolean verified = super.doVerification();

        try {
            if (!this.verifyNumberOfRandomnessValuesReceivedByPODPrinters(serialNumber)) {
                verified = false;
            }

            if (!this.verifyNumberOfRandomnessValuesCommittedToByMixServers(serialNumber)) {
                verified = false;
            }

            BallotAuditCommit auditCommit = null;

            // loop over each printer to audit
            for (CommitIdentifier identifier : this.getDataStore().getAuditData().keySet()) {
                auditCommit = this.getDataStore().getAuditData().get(identifier);

                if (auditCommit.getRandomnessCommitmentSerialNumbers().contains(serialNumber)) {

                    if (!this.verifyRandomness(serialNumber)) {
                        verified = false;
                    }

                    if (!this.combineRandomnessValues(serialNumber)) {
                        verified = false;
                    }

                    if (!this.verifyEncryptions(serialNumber)) {
                        verified = false;
                    }
                }
            }

        } catch (CommitException e) {
            logger.error("Unable to continue verification.", e);
            return false;
        }

        if (verified) {
            logger.debug(
                    "Ballot Generation Verification was carried out successfully for ballot with serial number: {}",
                    serialNumber);
            resultsLogger.info(
                    "Ballot Generation Verification was carried out successfully for ballot with serial number: {}",
                    serialNumber);
        } else {
            logger.debug(
                    "Ballot Generation Verification was carried out successfully for ballot with serial number: {}",
                    serialNumber);
            resultsLogger.info(
                    "Ballot Generation Verification was not carried out successfully for ballot with serial number: {}",
                    serialNumber);
        }

        return verified;
    }

    /**
     * Getter for the combined randomness values - these are computed by
     * performing hash computations on combined randomness values
     * 
     * @return the combined randomness values
     */
    private Map<String, List<MessageDigest>> getCombinedRandomness() {
        return this.combinedRandomness;
    }

    @Override
    public BallotGenDataStore getDataStore() {
        if (super.getDataStore() instanceof BallotGenDataStore) {
            return (BallotGenDataStore) super.getDataStore();
        }
        return null;
    }

    @Override
    public BallotGenerationVerifierSpec getSpec() {
        if (super.getSpec() instanceof BallotGenerationVerifierSpec) {
            return (BallotGenerationVerifierSpec) super.getSpec();
        }
        return null;
    }

    /**
     * Perform a re-encryption and sort on a sub section of the candidate ids
     * using the counters passed in
     * 
     * @param combinedRandomness
     * @param numberOfReencryptions
     * @param currentRandomIndex
     * @return a sorted and re-encrypted list of IndexedElGamalECPoint objects
     */
    private List<IndexedElGamalECPoint> reencryptAndSort(List<MessageDigest> combinedRandomness,
            int numberOfReencryptions, int currentRandomIndex) {

        logger.debug("Performing re-encryption and sorting for subsection of candidate ids");
        logger.debug("Number of re-encryptions to carry out: '{}'", numberOfReencryptions);
        logger.debug("Starting index for where to start re-encryptions: '{}'", currentRandomIndex);

        List<IndexedElGamalECPoint> result = new ArrayList<IndexedElGamalECPoint>();

        for (int i = 0; i < numberOfReencryptions; i++) {

            logger.debug("Re-encrypting candidate: '{}'", i + currentRandomIndex);

            // current random value
            BigInteger randValue = new BigInteger(1, combinedRandomness.get(i + currentRandomIndex).digest());

            // current base candidate identifier
            ElGamalECPoint baseCandidateId = this.getDataStore().getBaseEncryptedIds().get(i + currentRandomIndex);

            // current re-encrypted base candidate identifier
            ElGamalECPoint reencryptedCandidateId = ECUtils.reencrypt(baseCandidateId,
                    this.getDataStore().getPublicKey(), randValue);

            // indexed re-encrypted base candidate identifier storing the
            // original position
            IndexedElGamalECPoint indexedEC = new IndexedElGamalECPoint(reencryptedCandidateId, i);

            result.add(i, indexedEC);

        }

        logger.debug("Sorting re-encrypted candidate ids");
        Collections.sort(result, new ElGamalECPointComparator());

        return result;
    }

    /**
     * Performs verification of the re-encryptions and sorting of the base
     * encrypted candidate ids. A permutation string is also re-computed and
     * used in a hash commitment check.
     * 
     * @return true if the re-encryption and sorting takes place successfully.
     * @throws CommitException
     */
    public boolean verifyEncryptions() throws CommitException {

        BallotAuditCommit auditCommit = null;

        BallotGenerationRandomness currentBallotRandomness = null;

        // loop over each printer to audit
        for (CommitIdentifier identifier : this.getDataStore().getAuditData().keySet()) {
            auditCommit = this.getDataStore().getAuditData().get(identifier);

            // loop over the ballots to audit
            for (String serialNumber : auditCommit.getRandomnessCommitmentSerialNumbers()) {

                currentBallotRandomness = auditCommit.getRandomnessCommit(serialNumber);

                if (!this.verifyEncryptions(currentBallotRandomness, identifier)) {
                    return false;
                }

                auditCommit.freeRandomnessCommit(serialNumber);
            }
        }

        logger.debug("Re-encrypted and sorted base candidate ids match those commited to by the mix servers");
        resultsLogger.info("Re-encrypted and sorted base candidate ids match those commited to by the mix servers");

        return true;
    }

    /**
     * Performs the actual re-encryption and sorting of the base encrypted
     * candidate ids for a specific serial number. The committed permutation
     * string is then also checked using the combined randomness values
     * 
     * @param currentBallotRandomness
     * @param identifier
     * @return true if the re-encryption and sorting takes place successfully
     *         for the specific ballot
     * @throws CommitException
     */
    public boolean verifyEncryptions(BallotGenerationRandomness currentBallotRandomness,
            CommitIdentifier identifier) throws CommitException {

        final String serialNo = currentBallotRandomness.getSerialNo();

        logger.info(
                "Starting Verification of the encryptions and construction of the generic ballots for ballot with serial number: {} for PoD Printer: {}",
                serialNo, identifier.getPrinterId());

        // stores the re-encryptions
        List<IndexedElGamalECPoint> laReencryptions = null;
        List<IndexedElGamalECPoint> lcATLReencryptions = null;
        List<IndexedElGamalECPoint> lcBTLReencryptions = null;

        // stores the appropriate sizes of each race
        int[] sizes = new int[] { this.getDataStore().getBallotGenerationConfig().getLASize(),
                this.getDataStore().getBallotGenerationConfig().getLcATLSize(),
                this.getDataStore().getBallotGenerationConfig().getLcBTLSize() };

        // stores the sections of sorted re-encrypted base encrypted ids
        List<IndexedElGamalECPoint> combinedBallotCiphers = null;

        // stores the current combined randomness values
        List<MessageDigest> currentRandomnessList = null;

        // stores the details for the current committed cipher ballot
        CommittedBallot currentBallot = null;

        int numberOfReencryptions = 0;
        int currentRandomIndex = 0;

        byte[] witness = null;
        byte[] randomnessValue = null;
        byte[] commit = null;

        logger.debug("Veriyfing re-encryptions for client: '{}'", serialNo);

        // get the combined randomness values for the current serial
        // number/client
        currentRandomnessList = this.getCombinedRandomness().get(serialNo);

        // perform re-encryption and sorting on the LA race
        numberOfReencryptions = sizes[0];
        laReencryptions = this.reencryptAndSort(currentRandomnessList, numberOfReencryptions, currentRandomIndex);

        // perform re-encryption and sorting on the LC ATL race
        currentRandomIndex += numberOfReencryptions;
        numberOfReencryptions = sizes[1];
        lcATLReencryptions = this.reencryptAndSort(currentRandomnessList, numberOfReencryptions,
                currentRandomIndex);

        // perform re-encryption and sorting on the LC BTL race
        currentRandomIndex += numberOfReencryptions;
        numberOfReencryptions = sizes[2];
        lcBTLReencryptions = this.reencryptAndSort(currentRandomnessList, numberOfReencryptions,
                currentRandomIndex);

        // combine all re-encrypted and sorted ids together
        combinedBallotCiphers = new ArrayList<IndexedElGamalECPoint>();
        combinedBallotCiphers.addAll(laReencryptions);
        combinedBallotCiphers.addAll(lcATLReencryptions);
        combinedBallotCiphers.addAll(lcBTLReencryptions);

        // get the permutation string for each set of re-encryptions
        StringBuilder permutationString = new StringBuilder();
        permutationString.append(getPermutation(laReencryptions));
        permutationString.append(getPermutation(lcATLReencryptions));
        permutationString.append(getPermutation(lcBTLReencryptions));

        // get the current committed cipher ballot
        currentBallot = this.getDataStore().getGeneratedCiphers().get(identifier).getCommittedBallot(serialNo);

        logger.debug("Current commited ballot: '{}'", currentBallot);

        // loop through the cipher texts and check they match the
        // corresponding combined ballot
        for (int i = 0; i < currentBallot.getCiphers().size(); i++) {
            logger.debug("Checking ballot with serial number: '{}' and index: '{}'", serialNo, i);

            if (!currentBallot.getCiphers().get(i).equals(combinedBallotCiphers.get(i))) {

                logger.error(
                        "Committed cipher and combined ballot cipher for serial number: '{}' with index: '{}' do not match",
                        serialNo, i);
                resultsLogger.error(
                        "Committed cipher and combined ballot cipher for serial number: '{}' with index: '{}' do not match",
                        serialNo, i);

                return false;
            }

            logger.debug("Check was successful for ballot with serial number: '{}' and index: '{}'", serialNo, i);
        }

        logger.debug("Performing hash commitment check on ballot with serial number: '{}'", serialNo);

        // perform a hash commitment check using the last randomness value
        witness = currentRandomnessList.get(currentRandomnessList.size() - 1).digest();
        randomnessValue = permutationString.toString().getBytes();
        commit = Utils.decodeBase64Data(currentBallot.getPermutation());

        // check the hash commitment
        if (!CryptoUtils.verifyHashCommitment(commit, witness, randomnessValue)) {
            logger.error(
                    "Hash commitment check was unsucessfull for combined ballot cipher with serial number: '{}'",
                    serialNo);
            resultsLogger.error(
                    "Hash commitment check was unsucessfull for combined ballot cipher with serial number: '{}'",
                    serialNo);

            return false;
        }

        this.getDataStore().getGeneratedCiphers().get(identifier).freeCommittedBallot(serialNo);

        logger.debug(
                "Re-encryption and sorting was successful for ballot with serial number: '{}'. The generic ballot was generated successfully by PoD Printer: {}",
                serialNo, identifier.getPrinterId());
        resultsLogger.info(
                "Re-encryption and sorting was successful for ballot with serial number: '{}'. The generic ballot was generated successfully by PoD Printer: {}",
                serialNo, identifier.getPrinterId());

        return true;
    }

    /**
     * Verifies encryptions for a specific ballot with the provided serial
     * number
     * 
     * @param serialNumber
     * @return true if the encryptions were carried out successfully
     * @throws CommitException
     */
    public boolean verifyEncryptions(String serialNumber) throws CommitException {

        if (!this.isAuditBallot(serialNumber)) {
            logger.info(
                    "Ballot specified with serial number: {} was not chosen for auditing and it is therefore not possible to verify this ballot",
                    serialNumber);
            return false;
        }

        BallotAuditCommit auditCommit = null;

        BallotGenerationRandomness currentBallotRandomness = null;

        // loop over each printer to audit
        for (CommitIdentifier identifier : this.getDataStore().getAuditData().keySet()) {
            auditCommit = this.getDataStore().getAuditData().get(identifier);

            if (auditCommit.getRandomnessCommitmentSerialNumbers().contains(serialNumber)) {
                currentBallotRandomness = auditCommit.getRandomnessCommit(serialNumber);

                if (this.verifyEncryptions(currentBallotRandomness, identifier)) {
                    return true;
                }

                auditCommit.freeRandomnessCommit(serialNumber);
            }
        }

        return false;
    }

    /**
     * Verifies the fiat shamir calculation
     * 
     * @return true if the fiat shamir calculation matches that which is
     *         included in the ballot submit response message for the current
     *         commit
     */
    public boolean verifyFiatShamirCalculation() {

        logger.info(
                "Starting Verification of the Fiat Shamir signature which determines the ballots chosen for auditing by each PoD Printer");

        final int ballotsToGenerate = this.getDataStore().getBallotGenerationConfig().getBallotsToGenerate();
        final int ballotsToAudit = this.getDataStore().getBallotGenerationConfig().getBallotsToAudit();

        List<String> serialNumbers = null;

        BallotAuditCommit auditCommit = null;

        boolean verified = true;

        String currentBoothId = null;

        for (CommitIdentifier identifier : this.getDataStore().getAuditData().keySet()) {

            logger.info("Verifying the Fiat-Shamir signature for commitment with identifier: {}", identifier);

            serialNumbers = new ArrayList<String>(
                    this.getDataStore().getGeneratedCiphers().get(identifier).getCommittedBallotsSerialNumbers());

            // need to sort the serial numbers to make sure they are in a
            // 'default'
            // state i.e. in order
            Collections.sort(serialNumbers, new BallotSerialNumberComparator());

            // check generation size
            if (serialNumbers.size() != ballotsToGenerate) {
                logger.error(
                        "The number of ballots generated ({}) doesn't match the number of ballots requested for generation ({})",
                        serialNumbers.size(), ballotsToGenerate);
                resultsLogger.error(
                        "The number of ballots generated ({}) doesn't match the number of ballots requested for generation ({})",
                        serialNumbers.size(), ballotsToGenerate);

                return false;
            }

            auditCommit = this.getDataStore().getAuditData().get(identifier);

            currentBoothId = auditCommit.getMessage().getBoothID();

            // verify sig is created properly:
            try {
                if (!verifySignatureMatches(identifier, auditCommit)) {
                    verified = false;
                }
            } catch (NoSuchAlgorithmException | NoSuchProviderException | FileHashException e) {
                logger.error("The Fiat Shamir signature couldn't be calculated. Check the supplied data", e);
                resultsLogger.error("The Fiat Shamir signature couldn't be calculated. Check the supplied data", e);
                return false;
            }

            logger.info(
                    "Fiat shamir signature was recalculated and matches what was included in the BallotSubmitResponse message for printer: {}",
                    currentBoothId);

            logger.info(
                    "Verifying that the ballots chosen for auditing were correctly chosen for commitment with identifier: {}",
                    identifier);

            final byte[] fiatShamirSig = Utils.decodeBase64Data(auditCommit.getResponse().getFiatShamir());

            // use fiat shamir sig as the seed for the deterministic random bit
            // generator
            FixedSecureRandom fixedSecureRandom = new FixedSecureRandom(fiatShamirSig);

            SP800SecureRandomBuilder randomBuilder = new SP800SecureRandomBuilder(fixedSecureRandom, false);
            randomBuilder.setPersonalizationString(auditCommit.getResponse().getPeerID().getBytes());
            SP800SecureRandom sp800SecureRandom = randomBuilder.buildHash(new SHA256Digest(), null, false);

            Collections.shuffle(serialNumbers, sp800SecureRandom);

            ArrayList<String> serialNumbersToAudit = new ArrayList<String>();

            for (int i = 0; i < ballotsToAudit; i++) {
                serialNumbersToAudit.add(serialNumbers.get(i));
            }

            if (serialNumbersToAudit.size() != ballotsToAudit) {
                logger.error(
                        "The number of serial numbers calculated for auditing does not match the number of serial numbers requested for auditing for commitment with identifier: {}",
                        identifier);
                resultsLogger.error(
                        "The number of serial numbers calculated for auditing does not match the number of serial numbers requested for auditing for commitment with identifier: {}",
                        identifier);
                verified = false;
            }

            if (auditCommit.getRandomnessCommitmentSerialNumbers().size() != ballotsToAudit) {
                logger.error(
                        "The number of serial numbers included in the audit file doesn't match the number of serial numbers requested for auditing for commitment with identifier: {}",
                        identifier);
                resultsLogger.error(
                        "The number of serial numbers included in the audit file doesn't match the number of serial numbers requested for auditing for commitment with identifier: {}",
                        identifier);
                verified = false;
            }

            if (!auditCommit.getRandomnessCommitmentSerialNumbers().containsAll(serialNumbersToAudit)) {
                logger.error(
                        "The serial numbers included in the audit file do not match the serial numbers requested for auditing calculated using the fiat shamir signature for commitment with identifier: {}",
                        identifier);
                resultsLogger.error(
                        "The serial numbers included in the audit file do not match the serial numbers requested for auditing calculated using the fiat shamir signature for commitment with identifier: {}",
                        identifier);
                verified = false;
            }

            logger.debug(
                    "Successfully verified that the serial numbers of ballots for auditing were correctly chosen using the Fiat shamir signature for commitment with identifier: {}",
                    identifier);
            resultsLogger.info(
                    "Serial numbers for auditing were checked successfully using the Fiat shamir signature for commitment with identifier: {}",
                    identifier);
        }

        logger.debug(
                "Successfully verified the Fiat Shamir signatures were used to choose the required number of ballots for auditing");
        resultsLogger.info(
                "Successfully verified the Fiat Shamir signatures were used to choose the required number of ballots for auditing");

        return verified;
    }

    /**
     * Verifies the number of ballots to audit matches the number of ballots
     * represented/stored
     * 
     * @return true if the number of ballots to audit matches the number of
     *         ballots read in for auditing
     */
    public boolean verifyNumberOfBallotsToAudit() {

        logger.info(
                "Starting Verification that the number of ballots to required for auditing specified in the configuration file matches the number of ballots for auditing found");

        BallotAuditCommit auditCommit = null;

        final int ballotsToAudit = this.getDataStore().getBallotGenerationConfig().getBallotsToAudit();

        for (CommitIdentifier identifier : this.getDataStore().getAuditData().keySet()) {
            auditCommit = this.getDataStore().getAuditData().get(identifier);

            if (ballotsToAudit != auditCommit.getRandomnessCommitmentSerialNumbers().size()) {
                logger.error(
                        "The number of ballots required for auditing ({}) does not match the number of audit ballots found ({}) for printer: ({})",
                        ballotsToAudit, auditCommit.getRandomnessCommitmentSerialNumbers().size(),
                        identifier.getPrinterId());
                resultsLogger.error(
                        "The number of ballots required for auditing ({}) does not match the number of audit ballots found ({}) for printer: ({})",
                        ballotsToAudit, auditCommit.getRandomnessCommitmentSerialNumbers().size(),
                        identifier.getPrinterId());

                return false;
            }
        }

        logger.debug("Successfully verified that the number of ballots required for auditing were provided");
        resultsLogger.info("Successfully verified that the number of ballots required for auditing were provided");

        return true;
    }

    /**
     * Verifies the number of randomness values committed to for each mix server
     * 
     * @param serialNumber
     * 
     * @return true if the number of randomness values committed to for each mix
     *         server matches the expected number
     */
    public boolean verifyNumberOfRandomnessValuesCommittedToByMixServers(String serialNumber) {

        logger.info(
                "Starting Verification that the number of randomness values committed by the mix servers matches the number of candidates plus 1 for ballot with serial number: {}",
                serialNumber);

        if (!this.isAuditBallot(serialNumber)) {
            logger.info(
                    "Ballot specified with serial number: {} was not chosen for auditing and it is therefore not possible to verify this ballot",
                    serialNumber);
            return false;
        }

        Map<CommitIdentifier, List<MixRandomCommit>> serverMap = null;
        List<MixRandomCommit> commits = null;

        MixCommitData mixCommit = null;

        final int numberOfGenericCandidates = this.getDataStore().getNumberOfRandomnessValuesExpected();

        for (String serverName : this.getDataStore().getMixServerCommits().keySet()) {
            logger.debug("Checking randomness values sent by: {}", serverName);

            serverMap = this.getDataStore().getMixServerCommits().get(serverName);

            for (CommitIdentifier identifier : serverMap.keySet()) {
                logger.debug("Checking randomness values sent to: {}", identifier);

                commits = serverMap.get(identifier);

                for (MixRandomCommit currentCommit : commits) {

                    logger.debug("Checking randomness in submission with id: {}",
                            currentCommit.getMessage().getSubmissionId());

                    if (currentCommit.getServerCommits().hasMixRandomCommit(serialNumber)) {
                        mixCommit = currentCommit.getServerCommits().getMixRandomCommit(serialNumber);

                        // check the number of randomness values received
                        if (numberOfGenericCandidates != mixCommit.getNumberOfRandomnessValues()) {
                            logger.error(
                                    "The current number of randomness values received does not match the number of candidates plus 1. Server: '{}' for ballot: '{}'",
                                    serverName, mixCommit.getSerialNo());
                            resultsLogger.error(
                                    "The current number of randomness values received does not match the number of candidates plus 1. Server: '{}' for ballot: '{}'",
                                    serverName, mixCommit.getSerialNo());
                            return false;
                        }

                        currentCommit.getServerCommits().freeMixRandomCommit(serialNumber);
                    }
                }
            }
        }

        logger.debug(
                "Successfully verified that the number of randomness values committed to by the mix servers matches the number of candidates plus 1 for ballot with serial number: {}",
                serialNumber);
        resultsLogger.info(
                "Successfully verified that the number of randomness values committed to by the mix servers matches the number of candidates plus 1 for ballot with serial number: {}",
                serialNumber);

        return true;
    }

    /**
     * Verifies the number of randomness values received by the PoD Printers
     * 
     * @return true if the number of randomness values received matches that
     *         expected
     */
    public boolean verifyNumberOfRandomnessValuesReceivedByPODPrinters() {

        logger.info(
                "Starting Verification that the number of opened randomness commitments received by the PoD Printers matches the number of candidates plus 1");

        BallotAuditCommit auditCommit = null;
        BallotGenerationRandomness randomnessCommitment = null;

        boolean verified = true;

        final int numberOfCandidatesPlus1 = this.getDataStore().getNumberOfRandomnessValuesExpected();

        for (CommitIdentifier identifier : this.getDataStore().getAuditData().keySet()) {
            auditCommit = this.getDataStore().getAuditData().get(identifier);

            for (String serialNo : auditCommit.getRandomnessCommitmentSerialNumbers()) {
                randomnessCommitment = auditCommit.getRandomnessCommit(serialNo);

                for (OpenedRandomnessCommitments currentOpenedRandomness : randomnessCommitment
                        .getOpenedRandomnessValues()) {
                    // check the number of randomness values received
                    if (numberOfCandidatesPlus1 != currentOpenedRandomness.getNumRandomnessValues()) {
                        logger.error(
                                "The current number of opened randomness values ({}) for ballot with serial number: {} from printer: '{}' does not match the number of candidates plus 1 ({})",
                                currentOpenedRandomness.getNumRandomnessValues(), serialNo,
                                currentOpenedRandomness.getPeerId(), numberOfCandidatesPlus1);
                        resultsLogger.error(
                                "The current number of opened randomness values ({}) for ballot with serial number: {} from printer: '{}' does not match the number of candidates plus 1 ({})",
                                currentOpenedRandomness.getNumRandomnessValues(), serialNo,
                                currentOpenedRandomness.getPeerId(), numberOfCandidatesPlus1);
                        verified = false;
                    }
                }

                auditCommit.freeRandomnessCommit(serialNo);
            }
        }

        logger.debug(
                "Successfully verified that the number of opened randomness commitments received by each PoD Printer matches the number of candidates plus 1");
        resultsLogger.info(
                "Successfully verified that the number of opened randomness commitments received by each PoD Printer matches the number of candidates plus 1");

        return verified;
    }

    /**
     * Verifies the number of randomness values received by the PoD Printers
     * 
     * @param serialNumber
     * 
     * @return true if the number of randomness values received matches that
     *         expected
     */
    public boolean verifyNumberOfRandomnessValuesReceivedByPODPrinters(String serialNumber) {

        logger.info(
                "Starting Verification that the number of opened randomness commitments received by the PoD Printers matches the number of candidates plus 1 for ballot with serial number: {}",
                serialNumber);

        if (!this.isAuditBallot(serialNumber)) {
            logger.info(
                    "Ballot specified with serial number: {} was not chosen for auditing and it is therefore not possible to verify this ballot",
                    serialNumber);
            return false;
        }

        BallotAuditCommit auditCommit = null;
        BallotGenerationRandomness randomnessCommitment = null;

        final int numberOfCandidates = this.getDataStore().getNumberOfRandomnessValuesExpected();

        for (CommitIdentifier identifier : this.getDataStore().getAuditData().keySet()) {
            auditCommit = this.getDataStore().getAuditData().get(identifier);

            if (auditCommit.getRandomnessCommitmentSerialNumbers().contains(serialNumber)) {
                randomnessCommitment = auditCommit.getRandomnessCommit(serialNumber);

                for (OpenedRandomnessCommitments currentOpenedRandomness : randomnessCommitment
                        .getOpenedRandomnessValues()) {
                    // check the number of randomness values received
                    if (numberOfCandidates != currentOpenedRandomness.getNumRandomnessValues()) {
                        logger.error(
                                "The current number of opened randomness values ({}) for ballot with serial number: {} from printer: '{}' does not match the number of candidates plus 1 ({})",
                                currentOpenedRandomness.getNumRandomnessValues(), serialNumber,
                                currentOpenedRandomness.getPeerId(), numberOfCandidates);
                        resultsLogger.error(
                                "The current number of opened randomness values ({}) for ballot with serial number: {} from printer: '{}' does not match the number of candidates plus 1 ({})",
                                currentOpenedRandomness.getNumRandomnessValues(), serialNumber,
                                currentOpenedRandomness.getPeerId(), numberOfCandidates);
                        return false;
                    }
                }

                auditCommit.freeRandomnessCommit(serialNumber);
            }
        }

        logger.debug(
                "Successfully verified that the number of opened randomness commitments received by each PoD Printer matches the number of candidates plus 1 for ballot with serial number: {}",
                serialNumber);
        resultsLogger.info(
                "Successfully verified that the number of opened randomness commitments received by each PoD Printer matches the number of candidates plus 1 for ballot with serial number: {}",
                serialNumber);

        return true;
    }

    /**
     * Carry out the verification on the randomness values
     * 
     * We verify the randomness data stored by the PoD Printer (Once they have
     * opened their commitments) is consistent with those commitments made by
     * the Mix Servers - this can only be done once the commitments to the
     * randomness values have been opened by the PoD Printers which will only
     * happen if the ballot is chosen for auditing. The same ballot cannot then
     * be used for voting purposes
     * 
     * @return true if the randomness commitments made by the POD printers match
     *         the re-computed randomness values
     * @throws CommitException
     */
    public boolean verifyRandomness() throws CommitException {

        BallotAuditCommit auditCommit = null;

        BallotGenerationRandomness currentBallotRandomness = null;

        // loop over each printer to audit
        for (CommitIdentifier identifier : this.getDataStore().getAuditData().keySet()) {
            auditCommit = this.getDataStore().getAuditData().get(identifier);

            // loop over the ballots to audit
            for (String serialNumber : auditCommit.getRandomnessCommitmentSerialNumbers()) {

                currentBallotRandomness = auditCommit.getRandomnessCommit(serialNumber);

                if (!this.verifyRandomness(currentBallotRandomness, identifier)) {
                    return false;
                }

                auditCommit.freeRandomnessCommit(serialNumber);
            }
        }

        logger.debug(
                "The opened commitments from the POD printers match the randomness commitments made by the mix servers");
        resultsLogger.info(
                "The opened commitments from the POD printers match the randomness commitments made by the mix servers");

        return true;
    }

    /**
     * Verifies that the randomness values stored by a specified POD Printer
     * (using the serial number to reference a ballot created by them) - once it
     * has opened its commitment is consistent with the commitments made by the
     * mix servers.
     * 
     * @param currentBallotRandomness
     * @param identifier
     * @return true if the randomness values can be used to open the commitment
     *         made by the mix servers on the public wbb
     * @throws CommitException
     */
    public boolean verifyRandomness(BallotGenerationRandomness currentBallotRandomness, CommitIdentifier identifier)
            throws CommitException {

        String currentPeerID = null;
        Map<CommitIdentifier, List<MixRandomCommit>> mixServerMap = null;
        MixCommitData currentRandomnessCommit = null;
        RandomnessPair currentRandomPair = null;

        final String serialNo = currentBallotRandomness.getSerialNo();

        logger.info(
                "Starting Verification of the randomness values used by PoD Printer: {} for ballot with serial number: {} match the commitments made by the mix servers",
                identifier.getPrinterId(), serialNo);

        List<MixRandomCommit> currentServerCommits = null;
        RandomnessServerCommits currentServerRandomnessCommits = null;

        String commitment = null;
        String witness = null;
        String randomValue = null;

        boolean randomnessVerified = false;

        logger.debug("Veriyfing randomness values for ballot: '{}'", serialNo);

        // loop over the opened randomness commitments - each ballot to audit
        // may contain a number of OpenedRandomnessCommitments relating to
        // different mix servers (peers)
        for (OpenedRandomnessCommitments podOpenedRandomness : currentBallotRandomness
                .getOpenedRandomnessValues()) {

            randomnessVerified = false;

            // check that the serial numbers for each inner set match the
            // outer object - this should return positive
            logger.debug(
                    "Veriyfing the serial number for each of the inner opened commitments (for different peer ids) shares the same serial number");
            if (!podOpenedRandomness.getSerialNo().equals(serialNo)) {
                logger.error(
                        "The opened randomness commitments do not all share the same serial number as they should: first: '{}', second: '{}'",
                        podOpenedRandomness.getSerialNo(), serialNo);
                resultsLogger.error(
                        "The opened randomness commitments do not all share the same serial number as they should: first: '{}', second: '{}'",
                        podOpenedRandomness.getSerialNo(), serialNo);

                return false;
            }

            // get the current mix server id
            currentPeerID = podOpenedRandomness.getPeerId();

            logger.debug("Checking randomness for ballot: '{}', with mix server: '{}'", serialNo, currentPeerID);

            // get the current mix server commitments from the current peer
            // id - returns a representation of one of the files relating to
            // a peer/mix server
            mixServerMap = this.getDataStore().getMixServerCommits().get(currentPeerID);

            for (CommitIdentifier mixServerMapIdentifier : mixServerMap.keySet()) {
                if (mixServerMapIdentifier.getPrinterId().equals(identifier.getPrinterId())) {
                    currentServerCommits = mixServerMap.get(mixServerMapIdentifier);
                }
            }

            if (currentServerCommits != null) {

                for (int j = 0; j < currentServerCommits.size(); j++) {

                    if (!randomnessVerified) {

                        MixRandomCommit currentCommit = currentServerCommits.get(j);

                        currentServerRandomnessCommits = currentCommit.getServerCommits();

                        // gets the representation for the actual line from the
                        // file
                        // using the serial number
                        currentRandomnessCommit = currentServerRandomnessCommits.getMixRandomCommit(serialNo);

                        // loop over the randomness pairings for each of the
                        // sets of
                        // opened randomness commitments for each of the PoD
                        // Printers
                        for (int i = 0; i < podOpenedRandomness.getNumRandomnessValues(); i++) {

                            logger.debug(
                                    "Verifying hash commitment for ballot: '{}', with mix server: '{}', randomness index: '{}'",
                                    serialNo, currentPeerID, i);

                            // get the current pair
                            currentRandomPair = podOpenedRandomness.getRandomnessPair(i);

                            // get commitment from the current mix server file
                            commitment = currentRandomnessCommit.getRandomnessValue(i);

                            // get the witness and randomness value from the
                            // current
                            // pair
                            witness = currentRandomPair.getWitness();
                            randomValue = currentRandomPair.getRandomnessValue();

                            // check each commitment
                            if (!CryptoUtils.verifyHashCommitment(commitment, witness, randomValue)) {

                                if (j < currentServerCommits.size() - 1 && currentServerCommits.size() > 1) {
                                    resultsLogger.warn(
                                            "The commitment does not match the given witness and randomness value - The current Mix Server has multiple commitments so we will check the next commitment: {}",
                                            identifier);
                                    break;
                                }

                                logger.error(
                                        "The commitment does not match the given witness and randomness values - Commitment with identifier: {}, commitment: {}, witness: {}, randomness: {}",
                                        identifier, commitment, witness, randomValue);
                                resultsLogger.error(
                                        "The commitment does not match the given witness and randomness values - Commitment with identifier: {}, commitment: {}, witness: {}, randomness: {}",
                                        identifier, commitment, witness, randomValue);

                                return false;
                            }
                        }

                        currentServerRandomnessCommits.freeMixRandomCommit(serialNo);

                        randomnessVerified = true;
                    }
                }
            } else {
                logger.error("Could not locate the Mix server commit data for the current audit: {}", identifier);
                resultsLogger.error(
                        "The commitment does not match the given witness and randomness values - Commitment: {}, witness: {}, randomness: {}",
                        commitment, witness, randomValue);

                return false;
            }
            resultsLogger.info(
                    "Successfully verified that the randomness values for ballot: {} were provided by and committed to by mix server: {}",
                    serialNo, currentPeerID);
        }

        logger.debug(
                "Successfully verified that the randomness values for ballot: {} were provided by and committed to by the mix servers",
                serialNo);
        resultsLogger.info(
                "Successfully verified that the randomness values for ballot: {} were provided by and committed to by the mix servers",
                serialNo);

        return true;
    }

    /**
     * Verifies the randomness values for a single ballot with the provided
     * serial number
     * 
     * @param serialNumber
     * @return true if the randomness values are valid for the ballot with the
     *         specific serial number
     * @throws CommitException
     */
    public boolean verifyRandomness(String serialNumber) throws CommitException {

        if (!this.isAuditBallot(serialNumber)) {
            logger.info(
                    "Ballot specified with serial number: {} was not chosen for auditing and it is therefore not possible to verify this ballot",
                    serialNumber);
            return false;
        }

        BallotAuditCommit auditCommit = null;

        BallotGenerationRandomness currentBallotRandomness = null;

        // loop over each printer to audit
        for (CommitIdentifier identifier : this.getDataStore().getAuditData().keySet()) {
            auditCommit = this.getDataStore().getAuditData().get(identifier);

            if (auditCommit.getRandomnessCommitmentSerialNumbers().contains(serialNumber)) {
                currentBallotRandomness = auditCommit.getRandomnessCommit(serialNumber);

                if (!this.verifyRandomness(currentBallotRandomness, identifier)) {
                    return false;
                }

                auditCommit.freeRandomnessCommit(serialNumber);
            }
        }

        return true;
    }

    /**
     * Verifies that the fiat shamir signatures match - one is looked up from
     * the ballot submission response message and one is recalculated using
     * publicly available data
     * 
     * @param identifier
     * 
     * @param auditCommit
     * @return true if the signatures match
     * @throws NoSuchProviderException
     * @throws NoSuchAlgorithmException
     * @throws FileHashException
     */
    private boolean verifySignatureMatches(CommitIdentifier identifier, BallotAuditCommit auditCommit)
            throws NoSuchAlgorithmException, NoSuchProviderException, FileHashException {

        final String ciphersFilePath = this.getDataStore().getGeneratedCiphers().get(identifier)
                .getCiphersDataFilePath();

        final byte[] includedSig = Utils.decodeBase64Data(auditCommit.getResponse().getFiatShamir());

        MessageDigest fiatShamirDigest = MessageDigest.getInstance(
                CryptoConstants.FiatShamirSignature.FIAT_SHAMIR_HASH_ALGORITHM,
                CryptoConstants.FiatShamirSignature.FIAT_SHAMIR_PROVIDER);

        // add client id
        fiatShamirDigest.update(auditCommit.getResponse().getPeerID().getBytes());

        // add message id
        // message id of commit ciphers message BallotGenCommit
        fiatShamirDigest.update(auditCommit.getResponse().getSubmissionID().getBytes());

        // add commit time
        fiatShamirDigest.update(auditCommit.getMessage().getCommitTime().getBytes());

        // add ciphers file
        CryptoUtils.hashFile(new File(ciphersFilePath), fiatShamirDigest);

        // add combined sig
        String combinedSig = this.getCombinedSignature(this.getDataStore().getCertificatesFile(), auditCommit);
        fiatShamirDigest.update(Utils.decodeBase64Data(combinedSig));

        final byte[] fiatShamirSig = fiatShamirDigest.digest();

        // check whether the combined hash and the given commitment are
        // equal
        if (!Arrays.equals(fiatShamirSig, includedSig)) {
            logger.error(
                    "Calculated fiat shamir signature ({}) didn't match the one found in the ballot submit response message ({}) for PoD Printer: {} in commitment with identifier: {}",
                    Utils.byteToBase64String(fiatShamirSig), auditCommit.getResponse().getFiatShamir(),
                    auditCommit.getMessage().getBoothID(), identifier);
            resultsLogger.error(
                    "Calculated fiat shamir signature ({}) didn't match the one found in the ballot submit response message ({}) for PoD Printer: {} in commitment with identifier: {}",
                    Utils.byteToBase64String(fiatShamirSig), auditCommit.getResponse().getFiatShamir(),
                    auditCommit.getMessage().getBoothID(), identifier);
            return false;
        }

        return true;
    }

    /**
     * Gets the combined signature for the audit commitment
     * @param certificatesFile 
     * 
     * @param auditCommit
     * @return combined signature
     */
    private String getCombinedSignature(CertificatesFile certificatesFile, BallotAuditCommit auditCommit) {
        int numberOfPeers = 7;
        int threshold = 5;

        try {

            BLSCombiner bls = new BLSCombiner(numberOfPeers, threshold);

            int peerIndex = 0;

            for (WBBSignature sig : auditCommit.getResponse().getWbbSignatures()) {
                if (sig.isUsedAsPartOfThreshold()) {

                    peerIndex = this.getDataStore().getCertificatesFile().getSequenceNumberForPeer(sig.getWBBID());

                    bls.addShare(Utils.decodeBase64Data(sig.getWBBSig()), peerIndex);
                }
            }

            String combined = Utils.byteToBase64String(bls.combineSignatures().toBytes());

            return combined;
        } catch (BLSSignatureException e) {
            logger.error(
                    "Unable to get the combined signature for the current audit commitment: {} with BLS combiner({},{})",
                    auditCommit, numberOfPeers, threshold);
            return null;
        } catch (CertException e) {
            logger.error(
                    "Unable to get the combined signature for the current audit commitment: {} with BLS combiner({},{})",
                    auditCommit, numberOfPeers, threshold);
            return null;
        }
    }

    /**
     * Helper method to determine whether a ballot with the specified serial
     * number was chosen for auditing
     * 
     * @param serialNumber
     * @return true if the ballot with the specified serial number was chosen
     *         for auditing
     */
    public boolean isAuditBallot(String serialNumber) {

        logger.info(
                "Checking whether ballot with serial number: {} is a valid ballot which was chosen for auditing",
                serialNumber);

        BallotAuditCommit auditCommit = null;

        for (CommitIdentifier identifier : this.getDataStore().getAuditData().keySet()) {
            auditCommit = this.getDataStore().getAuditData().get(identifier);

            if (auditCommit.getRandomnessCommitmentSerialNumbers().contains(serialNumber)) {
                logger.info("Ballot with serial number: {} is a valid ballot which was chosen for auditing",
                        serialNumber);
                return true;
            }
        }
        logger.info("Ballot with serial number: {} was not chosen for auditing", serialNumber);
        return false;
    }
}