ch.ge.ve.protopoc.service.algorithm.DecryptionAuthorityAlgorithms.java Source code

Java tutorial

Introduction

Here is the source code for ch.ge.ve.protopoc.service.algorithm.DecryptionAuthorityAlgorithms.java

Source

/*-------------------------------------------------------------------------------------------------
 - #%L                                                                                            -
 - chvote-protocol-poc                                                                            -
 - %%                                                                                             -
 - Copyright (C) 2016 - 2017 Rpublique et Canton de Genve                                       -
 - %%                                                                                             -
 - 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 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/>.                           -
 - #L%                                                                                            -
 -------------------------------------------------------------------------------------------------*/

package ch.ge.ve.protopoc.service.algorithm;

import ch.ge.ve.protopoc.service.model.*;
import ch.ge.ve.protopoc.service.support.RandomGenerator;
import com.google.common.base.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static ch.ge.ve.protopoc.arithmetic.BigIntegerArithmetic.modExp;
import static ch.ge.ve.protopoc.service.support.BigIntegers.multiplyMod;
import static java.math.BigInteger.ONE;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toMap;

/**
 * Algorithms related to the decryption of ballots
 */
public class DecryptionAuthorityAlgorithms {
    private static final Logger log = LoggerFactory.getLogger(DecryptionAuthorityAlgorithms.class);
    private final PublicParameters publicParameters;
    private final GeneralAlgorithms generalAlgorithms;
    private final RandomGenerator randomGenerator;

    public DecryptionAuthorityAlgorithms(PublicParameters publicParameters, GeneralAlgorithms generalAlgorithms,
            RandomGenerator randomGenerator) {
        this.publicParameters = publicParameters;
        this.generalAlgorithms = generalAlgorithms;
        this.randomGenerator = randomGenerator;
    }

    /**
     * Algorithms 7.47: checkShuffleProofs
     *
     * @param bold_pi   the shuffle proofs generated by the authorities
     * @param e_0       the original encryption
     * @param bold_E    the vector of the re-encryption lists, per authority
     * @param publicKey the public key
     * @param j         the index of this authority
     * @return true if all the proofs generated by the other authorities are valid, false otherwise
     */
    public boolean checkShuffleProofs(List<ShuffleProof> bold_pi, List<Encryption> e_0,
            List<List<Encryption>> bold_E, EncryptionPublicKey publicKey, int j) {
        int s = publicParameters.getS();
        int N = e_0.size();
        Preconditions.checkArgument(bold_pi.size() == s, "there should be as many proofs as there are authorities");
        Preconditions.checkArgument(bold_E.size() == s,
                "there should be as many lists of re-encryptions as there are authorities");
        Preconditions.checkArgument(bold_E.stream().map(List::size).allMatch(l -> l == N),
                "every re-encryption list should have length N");
        Preconditions.checkElementIndex(j, s,
                "The index of the authority should be valid with respect to the number of authorities");

        // insert e_0 at index 0, thus offsetting all indices for bold_E by 1
        List<List<Encryption>> tmp_bold_e = new ArrayList<>();
        tmp_bold_e.add(0, e_0);
        tmp_bold_e.addAll(bold_E);
        for (int i = 0; i < s; i++) {
            if (i != j) {
                if (!checkShuffleProof(bold_pi.get(i), tmp_bold_e.get(i), tmp_bold_e.get(i + 1), publicKey)) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Algorithm 7.48: CheckShuffleProof
     *
     * @param pi           the proof of validity of the shuffle
     * @param bold_e       the vector of ElGamal encryptions
     * @param bold_e_prime the vector of permuted re-encryptions
     * @param publicKey    the system's public key
     * @return true if and only if the proof is valid for this shuffle
     */
    public boolean checkShuffleProof(ShuffleProof pi, List<Encryption> bold_e, List<Encryption> bold_e_prime,
            EncryptionPublicKey publicKey) {
        BigInteger pk = publicKey.getPublicKey();
        BigInteger p = publicParameters.getEncryptionGroup().getP();
        BigInteger q = publicParameters.getEncryptionGroup().getQ();
        BigInteger g = publicParameters.getEncryptionGroup().getG();
        BigInteger h = publicParameters.getEncryptionGroup().getH();
        int tau = publicParameters.getSecurityParameters().getTau();

        int N = bold_e.size();
        List<BigInteger> bold_c = pi.getBold_c();
        List<BigInteger> bold_c_hat = pi.getBold_c_hat();
        BigInteger t_1 = pi.getT().getT_1();
        BigInteger t_2 = pi.getT().getT_2();
        BigInteger t_3 = pi.getT().getT_3();
        List<BigInteger> t_4 = pi.getT().getT_4();
        List<BigInteger> t_hat = pi.getT().getT_hat();
        BigInteger s_1 = pi.getS().getS_1();
        BigInteger s_2 = pi.getS().getS_2();
        BigInteger s_3 = pi.getS().getS_3();
        BigInteger s_4 = pi.getS().getS_4();
        List<BigInteger> s_hat = pi.getS().getS_hat();
        List<BigInteger> s_prime = pi.getS().getS_prime();

        // Size checks
        Preconditions.checkArgument(bold_e_prime.size() == N,
                "The length of bold_e_prime should be identical to that of bold_e");
        Preconditions.checkArgument(bold_c.size() == N,
                "The length of bold_c should be identical to that of bold_e");
        Preconditions.checkArgument(bold_c_hat.size() == N,
                "The length of bold_c_hat should be identical to that of bold_e");
        Preconditions.checkArgument(t_4.size() == 2, "t_4 should contain two elements");
        Preconditions.checkArgument(t_hat.size() == N, "The length of t_hat should be identical to that of bold_e");
        Preconditions.checkArgument(s_hat.size() == N, "The length of s_hat should be identical to that of bold_e");
        Preconditions.checkArgument(s_prime.size() == N,
                "The length of s_prime should be identical to that of bold_e");

        // Validity checks
        Preconditions.checkArgument(generalAlgorithms.isMember(t_1), "t_1 must be in G_q");
        Preconditions.checkArgument(generalAlgorithms.isMember(t_2), "t_2 must be in G_q");
        Preconditions.checkArgument(generalAlgorithms.isMember(t_3), "t_3 must be in G_q");
        Preconditions.checkArgument(t_4.parallelStream().allMatch(generalAlgorithms::isMember),
                "t_4_1 and t_4_2 be in G_q");
        Preconditions.checkArgument(t_hat.parallelStream().allMatch(generalAlgorithms::isMember),
                "all t_hat_i's must be in G_q");
        Preconditions.checkArgument(generalAlgorithms.isInZ_q(s_1), "s_1 must be in Z_q");
        Preconditions.checkArgument(generalAlgorithms.isInZ_q(s_2), "s_2 must be in Z_q");
        Preconditions.checkArgument(generalAlgorithms.isInZ_q(s_3), "s_3 must be in Z_q");
        Preconditions.checkArgument(generalAlgorithms.isInZ_q(s_4), "s_4 must be in Z_q");
        Preconditions.checkArgument(s_hat.parallelStream().allMatch(generalAlgorithms::isInZ_q),
                "all s_hat_i's must be in Z_q");
        Preconditions.checkArgument(s_prime.parallelStream().allMatch(generalAlgorithms::isInZ_q),
                "all s_prime_i's must be in Z_q");
        Preconditions.checkArgument(bold_c.parallelStream().allMatch(generalAlgorithms::isMember),
                "all c_i's must be in G_q");
        Preconditions.checkArgument(bold_c_hat.parallelStream().allMatch(generalAlgorithms::isMember),
                "all c_hat_i's must be in G_q");
        Preconditions.checkArgument(
                bold_e.parallelStream().allMatch(
                        e -> generalAlgorithms.isMember(e.getA()) && generalAlgorithms.isMember(e.getB())),
                "all e_i's must be in G_q^2");
        Preconditions.checkArgument(
                bold_e_prime.parallelStream().allMatch(
                        e -> generalAlgorithms.isMember(e.getA()) && generalAlgorithms.isMember(e.getB())),
                "all e_prime_i's must be in G_q^2");
        Preconditions.checkArgument(generalAlgorithms.isMember(pk), "pk must be in G_q");

        List<BigInteger> bold_h = generalAlgorithms.getGenerators(N);
        List<BigInteger> bold_u = generalAlgorithms.getNIZKPChallenges(N,
                new List[] { bold_e, bold_e_prime, bold_c }, tau);
        Object[] y = { bold_e, bold_e_prime, bold_c, bold_c_hat, pk };
        BigInteger c = generalAlgorithms.getNIZKPChallenge(y, pi.getT().elementsToHash(), tau);

        BigInteger c_prod = bold_c.stream().reduce(multiplyMod(p)).orElse(ONE);
        BigInteger h_prod = bold_h.stream().reduce(multiplyMod(p)).orElse(ONE);
        BigInteger c_bar = c_prod.multiply(h_prod.modInverse(p)).mod(p);

        BigInteger u = bold_u.stream().reduce(multiplyMod(q)).orElse(ONE);

        BigInteger c_hat = bold_c_hat.get(N - 1).multiply(modExp(h, u.negate(), p));
        BigInteger c_tilde = IntStream.range(0, N).mapToObj(i -> modExp(bold_c.get(i), bold_u.get(i), p))
                .reduce(multiplyMod(p)).orElse(ONE);

        BigInteger e_prime_1 = IntStream.range(0, N).mapToObj(i -> modExp(bold_e.get(i).getA(), bold_u.get(i), p))
                .reduce(multiplyMod(p)).orElse(ONE);
        BigInteger e_prime_2 = IntStream.range(0, N).mapToObj(i -> modExp(bold_e.get(i).getB(), bold_u.get(i), p))
                .reduce(multiplyMod(p)).orElse(ONE);

        BigInteger t_prime_1 = modExp(c_bar, c.negate(), p).multiply(modExp(g, s_1, p)).mod(p);
        BigInteger t_prime_2 = modExp(c_hat, c.negate(), p).multiply(modExp(g, s_2, p)).mod(p);
        BigInteger h_i_s_prime_i = IntStream.range(0, N).parallel()
                .mapToObj(i -> modExp(bold_h.get(i), s_prime.get(i), p)).reduce(multiplyMod(p)).orElse(ONE);
        BigInteger t_prime_3 = modExp(c_tilde, c.negate(), p).multiply(modExp(g, s_3, p)).multiply(h_i_s_prime_i)
                .mod(p);

        BigInteger a_prime_i_s_prime_i = IntStream.range(0, N).parallel()
                .mapToObj(i -> modExp(bold_e_prime.get(i).getA(), s_prime.get(i), p)).reduce(multiplyMod(p))
                .orElse(ONE);
        BigInteger t_prime_4_1 = modExp(e_prime_1, c.negate(), p).multiply(modExp(pk, s_4.negate(), p))
                .multiply(a_prime_i_s_prime_i).mod(p);
        BigInteger b_prime_i_s_prime_i = IntStream.range(0, N).parallel()
                .mapToObj(i -> modExp(bold_e_prime.get(i).getB(), s_prime.get(i), p)).reduce(multiplyMod(p))
                .orElse(ONE);
        BigInteger t_prime_4_2 = modExp(e_prime_2, c.negate(), p).multiply(modExp(g, s_4.negate(), p))
                .multiply(b_prime_i_s_prime_i).mod(p);

        // add c_hat_0: h, thus offsetting the indices for c_hat by 1.
        List<BigInteger> tmp_bold_c_hat = new ArrayList<>();
        tmp_bold_c_hat.add(0, h);
        tmp_bold_c_hat.addAll(bold_c_hat);
        Map<Integer, BigInteger> t_hat_prime_map = IntStream.range(0, N).parallel().boxed()
                .collect(toMap(identity(),
                        i -> modExp(tmp_bold_c_hat.get(i + 1), c.negate(), p).multiply(modExp(g, s_hat.get(i), p))
                                .multiply(modExp(tmp_bold_c_hat.get(i), s_prime.get(i), p)).mod(p)));
        List<BigInteger> t_hat_prime = IntStream.range(0, N).mapToObj(t_hat_prime_map::get)
                .collect(Collectors.toList());

        boolean isProofValid = t_1.compareTo(t_prime_1) == 0 && t_2.compareTo(t_prime_2) == 0
                && t_3.compareTo(t_prime_3) == 0 && t_4.get(0).compareTo(t_prime_4_1) == 0
                && t_4.get(1).compareTo(t_prime_4_2) == 0
                && IntStream.range(0, N).map(i -> t_hat.get(i).compareTo(t_hat_prime.get(i))).allMatch(i -> i == 0);
        if (!isProofValid) {
            log.error("Invalid proof found");
        }
        return isProofValid;
    }

    /**
     * Algorithm 7.49: GetPartialDecryptions
     *
     * @param bold_e ElGamal encryption of the votes
     * @param sk_j   the decryption key share for authority j
     * @return the list of the partial decryptions of the provided ElGamal encryptions, using key share sk_j
     */
    public List<BigInteger> getPartialDecryptions(List<Encryption> bold_e, BigInteger sk_j) {
        Preconditions.checkArgument(
                bold_e.parallelStream().allMatch(
                        e -> generalAlgorithms.isMember(e.getA()) && generalAlgorithms.isMember(e.getB())),
                "all e_i's must be in G_q^2");
        BigInteger p = publicParameters.getEncryptionGroup().getP();
        return bold_e.stream().map(e_i -> modExp(e_i.getB(), sk_j, p)).collect(Collectors.toList());
    }

    /**
     * Algorithm 7.50: GenDecryptionProof
     *
     * @param sk_j         the private key share of authority j
     * @param pk_j         the public key share of authority j
     * @param bold_e       the vector of ElGamal encryptions
     * @param bold_b_prime the vector of partial ElGamal decryptions
     * @return a proof of knowledge for sk_j, satisfying <tt>b'_i = b_i ^ sk_j</tt> for all encryptions, and
     * <tt>pk_j = g ^ sk_j</tt>
     */
    public DecryptionProof genDecryptionProof(BigInteger sk_j, BigInteger pk_j, List<Encryption> bold_e,
            List<BigInteger> bold_b_prime) {
        Preconditions.checkArgument(generalAlgorithms.isInZ_q(sk_j), "sk_j must be in Z_q");
        Preconditions.checkArgument(generalAlgorithms.isMember(pk_j), "pk_j must be in G_q");
        Preconditions.checkArgument(
                bold_e.parallelStream().allMatch(
                        e -> generalAlgorithms.isMember(e.getA()) && generalAlgorithms.isMember(e.getB())),
                "all e_i's must be in G_q^2");
        Preconditions.checkArgument(bold_b_prime.parallelStream().allMatch(generalAlgorithms::isMember),
                "all b_prime_i's must be in G_q^2");

        BigInteger p = publicParameters.getEncryptionGroup().getP();
        BigInteger q = publicParameters.getEncryptionGroup().getQ();
        BigInteger g = publicParameters.getEncryptionGroup().getG();
        BigInteger omega = randomGenerator.randomInZq(q);
        int tau = publicParameters.getSecurityParameters().getTau();

        BigInteger t_0 = modExp(g, omega, p);
        List<BigInteger> t = bold_e.stream().map(e_i -> modExp(e_i.getB(), omega, p)).collect(Collectors.toList());
        t.add(0, t_0);
        List<BigInteger> bold_b = bold_e.stream().map(Encryption::getB).collect(Collectors.toList());
        Object[] y = { pk_j, bold_b, bold_b_prime };
        BigInteger c = generalAlgorithms.getNIZKPChallenge(y, t.toArray(new BigInteger[0]), tau);
        BigInteger s = omega.add(c.multiply(sk_j)).mod(q);

        return new DecryptionProof(t, s);
    }
}