ch.bfh.evoting.verifier.Verifier.java Source code

Java tutorial

Introduction

Here is the source code for ch.bfh.evoting.verifier.Verifier.java

Source

/* 
 * MobiVoteVerifier
 * 
 *  MobiVoteVerifier: Verification application for MobiVote
 *  Copyright (C) 2014 Bern
 *  University of Applied Sciences (BFH), Research Institute for Security
 *  in the Information Society (RISIS), E-Voting Group (EVG) Quellgasse 21,
 *  CH-2501 Biel, Switzerland
 * 
 *  GNU Affero General Public License (AGPL) v3
 *   
 *   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/>.
 * 
 * For further information contact us: http://e-voting.bfh.ch/
 *
 * Redistributions of files must retain the above copyright notice.
 */
package ch.bfh.evoting.verifier;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.codec.binary.Base64;
import org.simpleframework.xml.Serializer;
import org.simpleframework.xml.core.Persister;

import ch.bfh.evoting.verifier.entities.XMLOption;
import ch.bfh.evoting.verifier.entities.XMLParticipant;
import ch.bfh.evoting.verifier.entities.XMLPoll;
import ch.bfh.unicrypt.crypto.proofsystem.classes.ElGamalEncryptionValidityProofSystem;
import ch.bfh.unicrypt.crypto.proofsystem.classes.PreimageEqualityProofSystem;
import ch.bfh.unicrypt.crypto.proofsystem.classes.PreimageProofSystem;
import ch.bfh.unicrypt.crypto.schemes.commitment.classes.StandardCommitmentScheme;
import ch.bfh.unicrypt.crypto.schemes.encryption.classes.ElGamalEncryptionScheme;
import ch.bfh.unicrypt.helper.array.ByteArray;
import ch.bfh.unicrypt.math.algebra.concatenative.classes.ByteArrayElement;
import ch.bfh.unicrypt.math.algebra.concatenative.classes.ByteArrayMonoid;
import ch.bfh.unicrypt.math.algebra.dualistic.classes.N;
import ch.bfh.unicrypt.math.algebra.dualistic.classes.Z;
import ch.bfh.unicrypt.math.algebra.dualistic.classes.ZMod;
import ch.bfh.unicrypt.math.algebra.general.classes.FiniteByteArrayElement;
import ch.bfh.unicrypt.math.algebra.general.classes.ProductGroup;
import ch.bfh.unicrypt.math.algebra.general.classes.Subset;
import ch.bfh.unicrypt.math.algebra.general.classes.Tuple;
import ch.bfh.unicrypt.math.algebra.general.interfaces.Element;
import ch.bfh.unicrypt.math.algebra.general.interfaces.Group;
import ch.bfh.unicrypt.math.algebra.multiplicative.classes.GStarModElement;
import ch.bfh.unicrypt.math.algebra.multiplicative.classes.GStarModSafePrime;
import ch.bfh.unicrypt.math.function.classes.ProductFunction;
import ch.bfh.unicrypt.math.function.interfaces.Function;
import ch.bfh.unicrypt.random.classes.PseudoRandomOracle;
import ch.bfh.unicrypt.random.classes.ReferenceRandomByteSequence;

/**
 * Class implementing a verifier for MobiVote HKRS12 application
 * @author Philmon von Bergen
 *
 */
public class Verifier {

    private static final boolean DEBUG = false;
    private static GStarModSafePrime G_q;
    private static ZMod Z_q;
    private static Element generator;
    private static XMLPoll poll;
    private static Element[] representations;
    private static Tuple otherInput;
    private static Element[] a;
    private static Element[] b;
    private static Element[] h;
    private static Element[] hHat;
    private static Element[] hHatPowX;
    private static Element[] proofForX;
    private static Element[] validityProof;
    private static Element[] equalityProof;

    private static boolean recoveryNeeded = false;
    private static Z Zgroup;

    /**
     * This program is a verifier for MobiVote application with HKRS12 protocol
     * You have to indicate the XML file exported from the application as argument for this program.
     * You can also indicate the files of different participants. In this case, there will be checked if all files contain the same values.
     * @param args the file(s) containing the values of the protocol
     */
    public static void main(String[] args) {

        /*
         * Reading files given as parameters
         */
        if (args.length < 1) {
            //Not enough arguments
            System.out.println(
                    "You must indicate the location of the XML file containing the data to verify! You can also indicate the files of different participants.");
            return;
        } else if (args.length > 1) {
            //Make a digest of all files and compare them
            List<byte[]> digests = new ArrayList<byte[]>();
            for (String s : args) {
                try {
                    MessageDigest md = MessageDigest.getInstance("SHA-256");

                    InputStream is = Files.newInputStream(Paths.get(s));
                    DigestInputStream dis = new DigestInputStream(is, md);
                    @SuppressWarnings("unused")
                    int numBytes = -1;
                    while ((numBytes = dis.read()) != -1) {
                        dis.read();
                    }

                    byte[] digest = md.digest();
                    digests.add(digest);

                } catch (IOException e) {
                    System.out.println("One of the given files does not exists!");
                    return;
                } catch (NoSuchAlgorithmException e1) {
                    System.out.println("Unable to compute the digest of the file!");
                    return;
                }
            }
            System.out.print("Checking the similarity of the files...");
            for (byte[] b : digests) {
                for (byte[] b2 : digests) {
                    if (!Arrays.equals(b, b2)) {
                        System.out.print("\t\t\t\t\t\t\t\t\t\tWRONG\n");
                        System.out.println(
                                "The given files are not all identical! This means the content received by the participants was not the same.");
                        return;
                    }
                }
            }
            System.out.print("\t\t\t\t\t\t\t\t\t\tOK\n\n");
        } else if (args.length == 1) {
            //Print a warning
            System.out.println(
                    "BE CAREFUL: checking only one file ensure that the cryptographic values of the protocol are correct.\n"
                            + "But, it does NOT ensure that all users have the same result. This could happen when a participant missed some messages.\n"
                            + "This case is only covered by this verifier by giving multiple files as argument.\n");
        }

        Serializer serializer = new Persister();
        File source = new File(args[0]);

        poll = null;
        System.out.print("Reading file...");
        try {
            poll = serializer.read(XMLPoll.class, source);
            System.out.print("\t\t\t\t\t\t\t\t\t\t\t\t\tOK\n");
        } catch (Exception e) {
            System.out.print("\t\t\t\t\t\t\t\t\t\t\t\t\tFAILED\n");
            e.printStackTrace();
            return;
        }

        /*
         * Create the initializations needed by the protocol
         */
        G_q = GStarModSafePrime.getInstance(new BigInteger(poll.getP()));
        Z_q = G_q.getZModOrder();
        Zgroup = Z.getInstance();
        generator = poll.getGenerator().getValue(G_q);

        representations = new Element[poll.getOptions().size()];
        int i = 0;
        for (XMLOption op : poll.getOptions()) {
            representations[i] = op.getRepresentation().getValue(Z_q);
            i++;
        }

        System.out.println();
        System.out.println("Verifying vote: \"" + poll.getQuestion() + "\"");

        /*
         * Verify the dependence between cryptographic values and the vote properties
         */
        //Not used anymore, implicitly done with otherInputs of proofs
        //verifyDependencyTextCrypto();

        /*
         * Verify the proofs for the user
         */
        System.out.println();
        System.out.println("Verifying the proof for the participants...");
        a = new Element[poll.getParticipants().size()];
        b = new Element[poll.getParticipants().size()];
        h = new Element[poll.getParticipants().size()];
        hHat = new Element[poll.getParticipants().size()];
        hHatPowX = new Element[poll.getParticipants().size()];
        proofForX = new Element[poll.getParticipants().size()];
        validityProof = new Element[poll.getParticipants().size()];
        equalityProof = new Element[poll.getParticipants().size()];
        Tuple pollTuple = preparePollOtherInput(poll, representations, generator);

        int k = 0;
        for (XMLParticipant p : poll.getParticipants()) {
            System.out.println("\tParticipant " + p.getIdentification() + " (" + p.getUniqueId() + ")");

            if (p.getAi() != null) {
                a[k] = p.getAi().getValue(G_q);
                if (DEBUG)
                    System.out.println("Value a: " + a[k]);
            }
            if (p.getHi() != null) {
                h[k] = p.getHi().getValue(G_q);
                if (DEBUG)
                    System.out.println("Value h: " + h[k]);
            }
            if (p.getBi() != null) {
                b[k] = p.getBi().getValue(G_q);
                if (DEBUG)
                    System.out.println("Value b: " + b[k]);
            }
            if (p.getHiHat() != null) {
                hHat[k] = p.getHiHat().getValue(G_q);
                if (DEBUG)
                    System.out.println("Value h hat: " + hHat[k]);
            }
            if (p.getHiHatPowXi() != null) {
                hHatPowX[k] = p.getHiHatPowXi().getValue(G_q);
                if (DEBUG)
                    System.out.println("Value h hat ^ x: " + hHatPowX[k]);
            }

            if (p.getProofForXi() != null) {
                ProductGroup group = ProductGroup.getInstance(G_q, Zgroup, Z_q);

                proofForX[k] = p.getProofForXi().getValue(group);
            }

            if (p.getProofValidVote() != null) {

                Group[] tripleElement1Groups = new Group[poll.getOptions().size()];
                Group[] tripleElement2Groups = new Group[poll.getOptions().size()];
                Group[] tripleElement3Groups = new Group[poll.getOptions().size()];
                for (i = 0; i < poll.getOptions().size(); i++) {
                    tripleElement1Groups[i] = ProductGroup.getInstance(G_q, G_q);
                    tripleElement2Groups[i] = Zgroup;
                    tripleElement3Groups[i] = Z_q;
                }
                ProductGroup tripleElement1 = ProductGroup.getInstance(tripleElement1Groups);
                ProductGroup tripleElement2 = ProductGroup.getInstance(tripleElement2Groups);
                ProductGroup tripleElement3 = ProductGroup.getInstance(tripleElement3Groups);
                ProductGroup group = ProductGroup.getInstance(tripleElement1, tripleElement2, tripleElement3);

                validityProof[k] = p.getProofValidVote().getValue(group);

                if (DEBUG)
                    System.out.println("Proof of validity: " + validityProof[k]);
            }

            if (p.getProofForHiHat() != null) {
                recoveryNeeded = true;
                ProductGroup group = ProductGroup.getInstance(ProductGroup.getInstance(G_q, G_q), Zgroup, Z_q);

                equalityProof[k] = p.getProofForHiHat().getValue(group);
            }

            Tuple participantTuple = prepareParticipantOtherInput(p);
            otherInput = Tuple.getInstance(participantTuple, pollTuple);

            /*
             * Verify proof of knowledge 
             */
            System.out.print("\t\tVerifying proof of knowledge of x value...");
            verifyProofOfKnowledgeOfX(k);

            /*
             * Verify proof of validity 
             */
            if (validityProof[k] != null) {
                System.out.print("\t\tVerifying proof of valid vote...");
                verifyValidityProof(k);
            }

            /*
             * Verify proof of equality 
             */
            if (equalityProof[k] != null) {
                System.out.print("\t\tVerifying proof of equality between discrete logarithm g^x and h_hat^x...");
                verifyEqualityProof(k);
            }
            k++;
        }

        /**
         * Computing the result
         */
        System.out.println();
        System.out.print("Computing the result...");
        computeResult();

    }

    //   /**
    //    * Verify the dependence between cryptographic values and the vote properties
    //    */
    //   private static void verifyDependencyTextCrypto() {
    //      System.out.println();
    //      System.out.print("Verifying the dependence between cryptographic values and the vote properties...");
    //      String texts = poll.getQuestion();
    //
    //      for(XMLOption op:poll.getOptions()){
    //         texts += op.getText();
    //      }
    //
    //      Tuple tuple = Tuple.getInstance(representations);
    //      ByteArray representationsElement = tuple.getHashValue();
    //      ByteArrayElement textElement = ByteArrayMonoid.getInstance().getElement(texts.getBytes());
    //
    //      ByteBuffer buffer = ByteBuffer.allocate(textElement.getValue().getLength()+representationsElement.getLength());
    //      buffer.put(textElement.getValue().getAll());
    //      buffer.put(representationsElement.getAll());
    //      buffer.flip(); 
    //
    //      ReferenceRandomByteSequence rrs = PseudoRandomOracle.getInstance().getReferenceRandomByteSequence(ByteArray.getInstance(buffer.array()));
    //      GStarModElement verificationGenerator = G_q.getIndependentGenerator(1, rrs);
    //
    //      if(G_q.areEquivalent(generator, verificationGenerator)){
    //         System.out.print("\t\t\t\tOK\n");
    //      } else {
    //         System.out.print("\t\t\t\tFAILED\n");
    //         System.out.println("The given poll and the given cryptographic values don't match together.");
    //      }
    //
    //   }

    /**
     * Verify the proof of knowledge of the x value
     */
    private static void verifyProofOfKnowledgeOfX(int k) {
        if (DEBUG)
            System.out.println("\n\t\t\tValue of a: " + a[k]);
        if (DEBUG)
            System.out.println("\t\t\tValue of proof: " + proofForX[k]);
        if (DEBUG)
            System.out.println("\t\t\tValue of generator: " + generator);

        //Proof of knowledge of xi
        StandardCommitmentScheme cs = StandardCommitmentScheme.getInstance(generator);
        if (DEBUG)
            System.out.println("\t\t\tStandardCommitmentScheme: " + cs);

        if (DEBUG)
            System.out.println("\t\t\tOtherInput: " + otherInput);

        PreimageProofSystem spg = PreimageProofSystem.getInstance(cs.getCommitmentFunction(), otherInput);

        //Generator and index of the participant has also to be hashed in the proof
        if (DEBUG)
            System.out.println("\t\t\tVerify proof: " + spg.verify(proofForX[k], a[k]));
        if (DEBUG)
            System.out.println();
        if (!DEBUG) {
            if (spg.verify(proofForX[k], a[k])) {
                System.out.print("\t\t\t\t\t\t\tCORRECT\n");
            } else {
                System.out.print("\t\t\t\t\t\t\tWRONG\n");
            }
        }
    }

    /**
     * Verify the validity proof of the vote
     */
    private static void verifyValidityProof(int k) {
        Element[] possibleVotes = new Element[poll.getOptions().size()];
        for (int j = 0; j < possibleVotes.length; j++) {
            possibleVotes[j] = generator.selfApply(representations[j]);
        }

        ElGamalEncryptionScheme ees = ElGamalEncryptionScheme.getInstance(generator);

        Subset possibleVotesSet = Subset.getInstance(G_q, possibleVotes);
        ElGamalEncryptionValidityProofSystem vpg = ElGamalEncryptionValidityProofSystem.getInstance(ees, h[k],
                possibleVotesSet, otherInput);

        //simulate the ElGamal cipher text (a,b) = (ai,bi);
        Tuple publicInput = Tuple.getInstance(a[k], b[k]);

        if (DEBUG)
            System.out.println("Validity proof: " + validityProof[k]);
        if (vpg.verify(validityProof[k], publicInput)) {
            System.out.print("\t\t\t\t\t\t\t\tCORRECT\n");
        } else {
            System.out.print("\t\t\t\t\t\t\t\tWRONG\n");
        }
    }

    /**
     * Verify the proof of equality between discrete logarithms
     */
    private static void verifyEqualityProof(int k) {
        //Function g^r
        StandardCommitmentScheme cs3 = StandardCommitmentScheme.getInstance(generator);
        Function f1 = cs3.getCommitmentFunction();

        //Function h_hat^r
        StandardCommitmentScheme cs4 = StandardCommitmentScheme.getInstance(hHat[k]);
        Function f2 = cs4.getCommitmentFunction();

        ProductFunction f3 = ProductFunction.getInstance(f1, f2);

        PreimageEqualityProofSystem piepg = PreimageEqualityProofSystem.getInstance(f3, otherInput);

        Tuple publicInput3 = Tuple.getInstance(a[k], hHatPowX[k]);

        if (piepg.verify(equalityProof[k], publicInput3)) {
            System.out.print("\t\t\tCORRECT\n");
        } else {
            System.out.print("\t\t\tWRONG\n");
        }
    }

    /**
     * Recompute the result with the values contained in the XML file, and compare the obtained result
     * with the result described in the XML file
     */
    private static void computeResult() {
        Element product1 = G_q.getElement(BigInteger.valueOf(1));
        for (int j = 0; j < poll.getParticipants().size(); j++) {
            if (b[j] == null)
                continue;

            if (recoveryNeeded) {

                if (hHatPowX[j] == null) {
                    //this was an excluded participant so we don't care about his values in the tally
                    continue;
                }
                product1 = product1.apply(b[j]);
                product1 = product1.apply(hHatPowX[j]);
            } else {
                product1 = product1.apply(b[j]);
            }
        }

        Element sumVote = Z_q.getElement(BigInteger.valueOf(0));
        for (int j = 0; j < poll.getOptions().size(); j++) {
            sumVote = sumVote.apply(representations[j].selfApply(poll.getOptions().get(j).getVotes()));
        }

        Element product2 = generator.selfApply(sumVote);

        if (G_q.areEquivalent(product1, product2)) {
            System.out.print("\t\t\t\t\t\t\t\t\t\t\t\tCORRECT\n");
            System.out.println("\tQuestion was: " + poll.getQuestion());
            System.out.println("\tResult was:");
            for (XMLOption op : poll.getOptions()) {
                String votesText = " votes";
                if (op.getVotes() == 1) {
                    votesText = " vote";
                }
                System.out.println("\t\t" + op.getText() + ": " + op.getVotes() + votesText);
            }
        } else {
            System.out.print("\t\t\t\t\t\t\t\t\t\t\t\tWRONG\n");
        }
    }

    /**
     * Return all the important data of the participant that should be used in the hash used in ZK proofs
     * @param p participant
     * @return all the important data of the participant that should be used in the hash used in ZK proofs
     */
    private static Tuple prepareParticipantOtherInput(XMLParticipant p) {
        Element index = N.getInstance().getElement(BigInteger.valueOf(p.getProtocolParticipantIndex()));
        ByteArrayElement proverId = ByteArrayMonoid.getInstance().getElement(p.getUniqueId().getBytes());

        return Tuple.getInstance(index, proverId);
    }

    /**
     * Return all the important data of the poll that should be used in the hash used in ZK proofs
     * @param poll Poll object
     * @param optionsRepresentations Representation values of the options
     * @param generator Generator used in the protocol
     * @return Return all the important data of the poll that should be used in the hash used in ZK proofs
     */
    private static Tuple preparePollOtherInput(XMLPoll poll, Element[] optionsRepresentations, Element generator) {
        String otherHashInputString = poll.getQuestion();
        for (XMLOption op : poll.getOptions()) {
            otherHashInputString += op.getText();
        }
        for (XMLParticipant p : poll.getParticipants()) {
            otherHashInputString += p.getUniqueId() + p.getIdentification();
        }

        ByteArrayElement otherHashInput = ByteArrayMonoid.getInstance().getElement(otherHashInputString.getBytes());
        Tuple optionsRepresentationsTuple = Tuple.getInstance(optionsRepresentations);
        return Tuple.getInstance(optionsRepresentationsTuple, otherHashInput, generator);

    }

}