/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
 */
package com.scytl.cryptolib.proofs.maurer.factory;

import java.util.List;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.commons.utils.validations.Validate;
import com.scytl.cryptolib.elgamal.bean.ElGamalPrivateKey;
import com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey;
import com.scytl.cryptolib.elgamal.cryptoapi.Ciphertext;
import com.scytl.cryptolib.elgamal.cryptoapi.Witness;
import com.scytl.cryptolib.mathematical.groups.MathematicalGroup;
import com.scytl.cryptolib.mathematical.groups.impl.ZpGroupElement;
import com.scytl.cryptolib.mathematical.groups.impl.ZpSubgroup;
import com.scytl.cryptolib.primitives.messagedigest.configuration.ConfigMessageDigestAlgorithmAndProvider;
import com.scytl.cryptolib.primitives.securerandom.factory.CryptoRandomInteger;
import com.scytl.cryptolib.proofs.bean.ProofPreComputedValues;
import com.scytl.cryptolib.proofs.cryptoapi.ProofProverAPI;
import com.scytl.cryptolib.proofs.maurer.configuration.ConfigProofHashCharset;
import com.scytl.cryptolib.proofs.proof.Proof;
import com.scytl.cryptolib.securerandom.cryptoapi.CryptoAPIRandomInteger;

/**
 * Implementation of {@link ProofProverAPI}, which allows for the creation of
 * several types of proofs.
 */
public final class ZpSubgroupProofProver implements ProofProverAPI {

    private final ZpSubgroup _group;

    private final ConfigMessageDigestAlgorithmAndProvider _configMessageDigestAlgorithmAndProvider;

    private final ConfigProofHashCharset _configProofHashCharset;

    private final CryptoAPIRandomInteger _cryptoRandomInteger;

    /**
     * Default constructor.
     * 
     * @param group
     *            the Zp subgroup used for ElGamal encryption.
     * @param configMessageDigestAlgorithmAndProvider
     *            the algorithm and provider of the message digest used to
     *            generate the proof.
     * @param configProofHashCharset
     *            the character set of the hash used to generate the proof.
     * @param cryptoRandomInteger
     *            the random number generator used to generate the proof.
     */
    ZpSubgroupProofProver(final MathematicalGroup<?> group,
            final ConfigMessageDigestAlgorithmAndProvider configMessageDigestAlgorithmAndProvider,
            final ConfigProofHashCharset configProofHashCharset,
            final CryptoRandomInteger cryptoRandomInteger) {

        _group = (ZpSubgroup) group;
        _configMessageDigestAlgorithmAndProvider =
            configMessageDigestAlgorithmAndProvider;
        _configProofHashCharset = configProofHashCharset;
        _cryptoRandomInteger = cryptoRandomInteger;
    }

    @Override
    public Proof createSchnorrProof(final String voterID,
            final String electionEventID,
            final ZpGroupElement exponentiatedGenerator,
            final Witness witness) throws GeneralCryptoLibException {

        validateSchnorrProofGeneratorInput(voterID, electionEventID,
            exponentiatedGenerator, witness);

        ProofPreComputedValues preComputedValues =
            new SchnorrProofPreComputer<>(_group,
                _configMessageDigestAlgorithmAndProvider,
                _configProofHashCharset).preCompute(_cryptoRandomInteger);

        return new SchnorrProofGenerator<>(voterID, electionEventID,
            exponentiatedGenerator, witness.getExponent(), _group,
            _configMessageDigestAlgorithmAndProvider,
            _configProofHashCharset, preComputedValues).generate();
    }

    @Override
    public Proof createSchnorrProof(final String voterID,
            final String electionEventID,
            final ZpGroupElement exponentiatedGenerator,
            final Witness witness,
            final ProofPreComputedValues preComputedValues)
            throws GeneralCryptoLibException {

        validateSchnorrProofGeneratorInput(voterID, electionEventID,
            exponentiatedGenerator, witness);

        Validate.notNull(preComputedValues, "Pre-computed values object");

        return new SchnorrProofGenerator<>(voterID, electionEventID,
            exponentiatedGenerator, witness.getExponent(), _group,
            _configMessageDigestAlgorithmAndProvider,
            _configProofHashCharset, preComputedValues).generate();
    }

    @Override
    public Proof createExponentiationProof(
            final List<ZpGroupElement> exponentiatedElements,
            final List<ZpGroupElement> baseElements, final Witness witness)
            throws GeneralCryptoLibException {

        validateExponentiationProofGeneratorInput(exponentiatedElements,
            baseElements, witness);

        ProofPreComputedValues preComputedValues =
            new ExponentiationProofPreComputer<>(baseElements, _group,
                _configMessageDigestAlgorithmAndProvider,
                _configProofHashCharset).preCompute(_cryptoRandomInteger);

        return new ExponentiationProofGenerator<>(exponentiatedElements,
            baseElements, witness.getExponent(), _group,
            _configMessageDigestAlgorithmAndProvider,
            _configProofHashCharset, preComputedValues).generate();
    }

    @Override
    public Proof createExponentiationProof(
            final List<ZpGroupElement> exponentiatedElements,
            final List<ZpGroupElement> baseElements, final Witness witness,
            final ProofPreComputedValues preComputedValues)
            throws GeneralCryptoLibException {

        validateExponentiationProofGeneratorInput(exponentiatedElements,
            baseElements, witness);

        Validate.notNull(preComputedValues, "Pre-computed values object");

        return new ExponentiationProofGenerator<>(exponentiatedElements,
            baseElements, witness.getExponent(), _group,
            _configMessageDigestAlgorithmAndProvider,
            _configProofHashCharset, preComputedValues).generate();
    }

    @Override
    public Proof createDecryptionProof(final ElGamalPublicKey publicKey,
            final Ciphertext ciphertext,
            final List<ZpGroupElement> plaintext,
            final ElGamalPrivateKey privateKey)
            throws GeneralCryptoLibException {

        validateDecryptionProofGeneratorInput(publicKey, ciphertext,
            plaintext, privateKey);

        ProofPreComputedValues preComputedValues =
            new DecryptionProofPreComputer<>(privateKey.getKeys().size(),
                ciphertext.getGamma(), _group,
                _configMessageDigestAlgorithmAndProvider,
                _configProofHashCharset).preCompute(_cryptoRandomInteger);

        return new DecryptionProofGenerator<>(publicKey,
            ciphertext.getElements(), plaintext, privateKey, _group,
            _configMessageDigestAlgorithmAndProvider,
            _configProofHashCharset, preComputedValues).generate();
    }

    @Override
    public Proof createDecryptionProof(final ElGamalPublicKey publicKey,
            final Ciphertext ciphertext,
            final List<ZpGroupElement> plaintext,
            final ElGamalPrivateKey privateKey,
            final ProofPreComputedValues preComputedValues)
            throws GeneralCryptoLibException {

        validateDecryptionProofGeneratorInput(publicKey, ciphertext,
            plaintext, privateKey);

        Validate.notNull(preComputedValues, "Pre-computed values object");

        return new DecryptionProofGenerator<>(publicKey,
            ciphertext.getElements(), plaintext, privateKey, _group,
            _configMessageDigestAlgorithmAndProvider,
            _configProofHashCharset, preComputedValues).generate();
    }

    @Override
    public Proof createPlaintextProof(final ElGamalPublicKey publicKey,
            final Ciphertext ciphertext,
            final List<ZpGroupElement> plaintext, final Witness witness)
            throws GeneralCryptoLibException {

        validatePlaintextProofGeneratorInput(publicKey, ciphertext,
            plaintext, witness);

        ProofPreComputedValues preComputedValues =
            new PlaintextProofPreComputer<>(publicKey, _group,
                _configMessageDigestAlgorithmAndProvider,
                _configProofHashCharset).preCompute(_cryptoRandomInteger);

        return new PlaintextProofGenerator<>(publicKey,
            ciphertext.getElements(), plaintext, witness.getExponent(),
            _group, _configMessageDigestAlgorithmAndProvider,
            _configProofHashCharset, preComputedValues).generate();
    }

    @Override
    public Proof createPlaintextProof(final ElGamalPublicKey publicKey,
            final Ciphertext ciphertext,
            final List<ZpGroupElement> plaintext, final Witness witness,
            final ProofPreComputedValues preComputedValues)
            throws GeneralCryptoLibException {

        validatePlaintextProofGeneratorInput(publicKey, ciphertext,
            plaintext, witness);

        Validate.notNull(preComputedValues, "Pre-computed values object");

        return new PlaintextProofGenerator<>(publicKey,
            ciphertext.getElements(), plaintext, witness.getExponent(),
            _group, _configMessageDigestAlgorithmAndProvider,
            _configProofHashCharset, preComputedValues).generate();
    }

    @Override
    public Proof createSimplePlaintextEqualityProof(
            final Ciphertext primaryCiphertext,
            final ElGamalPublicKey primaryPublicKey, final Witness witness,
            final Ciphertext secondaryCiphertext,
            final ElGamalPublicKey secondaryPublicKey)
            throws GeneralCryptoLibException {

        validateSimplePlaintextEqualityProofGeneratorInput(
            primaryCiphertext, primaryPublicKey, witness,
            secondaryCiphertext, secondaryPublicKey);

        ProofPreComputedValues preComputedValues =
            new SimplePlaintextEqualityProofPreComputer<>(primaryPublicKey,
                secondaryPublicKey, _group,
                _configMessageDigestAlgorithmAndProvider,
                _configProofHashCharset).preCompute(_cryptoRandomInteger);

        return new SimplePlaintextEqualityProofGenerator<>(
            primaryCiphertext.getElements(), primaryPublicKey,
            witness.getExponent(), secondaryCiphertext.getElements(),
            secondaryPublicKey, _group,
            _configMessageDigestAlgorithmAndProvider,
            _configProofHashCharset, preComputedValues).generate();
    }

    @Override
    public Proof createSimplePlaintextEqualityProof(
            final Ciphertext primaryCiphertext,
            final ElGamalPublicKey primaryPublicKey, final Witness witness,
            final Ciphertext secondaryCiphertext,
            final ElGamalPublicKey secondaryPublicKey,
            final ProofPreComputedValues preComputedValues)
            throws GeneralCryptoLibException {

        validateSimplePlaintextEqualityProofGeneratorInput(
            primaryCiphertext, primaryPublicKey, witness,
            secondaryCiphertext, secondaryPublicKey);

        Validate.notNull(preComputedValues, "Pre-computed values object");

        return new SimplePlaintextEqualityProofGenerator<>(
            primaryCiphertext.getElements(), primaryPublicKey,
            witness.getExponent(), secondaryCiphertext.getElements(),
            secondaryPublicKey, _group,
            _configMessageDigestAlgorithmAndProvider,
            _configProofHashCharset, preComputedValues).generate();
    }

    /**
     * @deprecated
     *
     */
    @Override
    @Deprecated
    public Proof createPlaintextEqualityProof(
            final List<ZpGroupElement> primaryCiphertext,
            final ElGamalPublicKey primaryPublicKey,
            final Witness primaryWitness,
            final List<ZpGroupElement> secondaryCiphertext,
            final ElGamalPublicKey secondaryPublicKey,
            final Witness secondaryWitness)
            throws GeneralCryptoLibException {

        Validate.notNull(primaryCiphertext, "Primary ciphertext");
        Validate.notNull(primaryPublicKey, "Primary ElGamal public key");
        Validate.notNull(primaryWitness, "Primary witness");
        Validate.notNull(secondaryCiphertext, "Secondary ciphertext");
        Validate.notNull(secondaryPublicKey,
            "Secondary ElGamal public key");
        Validate.notNull(secondaryWitness, "Secondary witness");

        ProofPreComputedValues preComputedValues =
            new PlaintextEqualityProofPreComputer<>(primaryPublicKey,
                secondaryPublicKey, _group,
                _configMessageDigestAlgorithmAndProvider,
                _configProofHashCharset).preCompute(_cryptoRandomInteger);

        return new PlaintextEqualityProofGenerator<>(primaryCiphertext,
            primaryPublicKey, primaryWitness.getExponent(),
            secondaryCiphertext, secondaryPublicKey,
            secondaryWitness.getExponent(), _group,
            _configMessageDigestAlgorithmAndProvider,
            _configProofHashCharset, preComputedValues).generate();
    }

    @Override
    public Proof createPlaintextEqualityProof(
            final Ciphertext primaryCiphertext,
            final ElGamalPublicKey primaryPublicKey,
            final Witness primaryWitness,
            final Ciphertext secondaryCiphertext,
            final ElGamalPublicKey secondaryPublicKey,
            final Witness secondaryWitness)
            throws GeneralCryptoLibException {

        validatePlaintextEqualityProofGeneratorInput(primaryCiphertext,
            primaryPublicKey, primaryWitness, secondaryCiphertext,
            secondaryPublicKey, secondaryWitness);

        ProofPreComputedValues preComputedValues =
            new PlaintextEqualityProofPreComputer<>(primaryPublicKey,
                secondaryPublicKey, _group,
                _configMessageDigestAlgorithmAndProvider,
                _configProofHashCharset).preCompute(_cryptoRandomInteger);

        return new PlaintextEqualityProofGenerator<>(
            primaryCiphertext.getElements(), primaryPublicKey,
            primaryWitness.getExponent(),
            secondaryCiphertext.getElements(), secondaryPublicKey,
            secondaryWitness.getExponent(), _group,
            _configMessageDigestAlgorithmAndProvider,
            _configProofHashCharset, preComputedValues).generate();
    }

    @Override
    public Proof createPlaintextEqualityProof(
            final Ciphertext primaryCiphertext,
            final ElGamalPublicKey primaryPublicKey,
            final Witness primaryWitness,
            final Ciphertext secondaryCiphertext,
            final ElGamalPublicKey secondaryPublicKey,
            final Witness secondaryWitness,
            final ProofPreComputedValues preComputedValues)
            throws GeneralCryptoLibException {

        validatePlaintextEqualityProofGeneratorInput(primaryCiphertext,
            primaryPublicKey, primaryWitness, secondaryCiphertext,
            secondaryPublicKey, secondaryWitness);

        Validate.notNull(preComputedValues, "Pre-computed values object");

        return new PlaintextEqualityProofGenerator<>(
            primaryCiphertext.getElements(), primaryPublicKey,
            primaryWitness.getExponent(),
            secondaryCiphertext.getElements(), secondaryPublicKey,
            secondaryWitness.getExponent(), _group,
            _configMessageDigestAlgorithmAndProvider,
            _configProofHashCharset, preComputedValues).generate();
    }

    @Override
    public Proof createPlaintextExponentEqualityProof(
            final Ciphertext ciphertext,
            final List<ZpGroupElement> plaintext, final Witness witness1,
            final Witness witness2) throws GeneralCryptoLibException {

        validatePlaintextExponentEqualityProofGeneratorInput(ciphertext,
            plaintext, witness1, witness2);

        ProofPreComputedValues preComputedValues =
            new PlaintextExponentEqualityProofPreComputer<>(plaintext,
                _group, _configMessageDigestAlgorithmAndProvider,
                _configProofHashCharset).preCompute(_cryptoRandomInteger);

        return new PlaintextExponentEqualityProofGenerator<>(
            ciphertext.getElements(), plaintext, witness1.getExponent(),
            witness2.getExponent(), _group,
            _configMessageDigestAlgorithmAndProvider,
            _configProofHashCharset, preComputedValues).generate();
    }

    @Override
    public Proof createPlaintextExponentEqualityProof(
            final Ciphertext ciphertext,
            final List<ZpGroupElement> baseElements,
            final Witness witness1, final Witness witness2,
            final ProofPreComputedValues preComputedValues)
            throws GeneralCryptoLibException {

        validatePlaintextExponentEqualityProofGeneratorInput(ciphertext,
            baseElements, witness1, witness2);

        Validate.notNull(preComputedValues, "Pre-computed values object");

        return new PlaintextExponentEqualityProofGenerator<>(
            ciphertext.getElements(), baseElements, witness1.getExponent(),
            witness2.getExponent(), _group,
            _configMessageDigestAlgorithmAndProvider,
            _configProofHashCharset, preComputedValues).generate();
    }

    @Override
    public Proof createORProof(Ciphertext ciphertext,
            ElGamalPublicKey publicKey, Witness witness, int index,
            List<ZpGroupElement> elements, String data)
            throws GeneralCryptoLibException {
        validateORProofGeneratorInput(ciphertext, publicKey, witness,
            index, elements, data);

        ProofPreComputedValues preComputedValues =
            new ORProofPreComputer(publicKey, elements.size())
                .preCompute(_cryptoRandomInteger);

        return new ORProofGenerator(ciphertext, publicKey, witness, index,
            elements, data, _cryptoRandomInteger,
            _configMessageDigestAlgorithmAndProvider,
            _configProofHashCharset, preComputedValues).generate();
    }

    @Override
    public Proof createORProof(Ciphertext ciphertext,
            ElGamalPublicKey publicKey, Witness witness, int index,
            List<ZpGroupElement> elements, String data,
            ProofPreComputedValues preComputedValues)
            throws GeneralCryptoLibException {
        validateORProofGeneratorInput(ciphertext, publicKey, witness,
            index, elements, data);

        Validate.notNull(preComputedValues, "Pre-computed values object");
        Validate.equals(preComputedValues.getExponents().size(),
            elements.size(), "number of exponents", "number of elements");
        Validate.equals(preComputedValues.getPhiOutputs().size(),
            elements.size() * 2, "number of phis",
            "doubled number of elements");

        return new ORProofGenerator(ciphertext, publicKey, witness, index,
            elements, data, _cryptoRandomInteger,
            _configMessageDigestAlgorithmAndProvider,
            _configProofHashCharset, preComputedValues).generate();
    }

    private void validateORProofGeneratorInput(Ciphertext ciphertext,
            ElGamalPublicKey publicKey, Witness witness, int index,
            List<ZpGroupElement> elements, String data)
            throws GeneralCryptoLibException {
        Validate.notNull(ciphertext, "Ciphertext");
        Validate.notNull(publicKey, "ElGamal public key");
        Validate.notNull(witness, "Witness");
        Validate.notNullOrEmptyAndNoNulls(elements, "Elements");
        Validate.notNull(data, "Data");
        Validate.notLessThan(index, 0, "Index",
            "index of the first element");
        Validate.notGreaterThan(index, elements.size() - 1, "Index",
            "index of the last element");
        Validate.equals(ciphertext.size(), 2, "elements size",
            "single key ciphertext size");
        Validate.equals(publicKey.getKeys().size(), 1, "key size",
            "single key size");
        Validate.equals(publicKey.getGroup(), _group, "key group",
            "prover's group");
        Validate.equals(witness.getExponent().getQ(), _group.getQ(),
            "witness' Q", "prover's Q");
    }

    private void validateSchnorrProofGeneratorInput(final String voterID,
            final String electionEventID,
            final ZpGroupElement exponentiatedGenerator,
            final Witness witness) throws GeneralCryptoLibException {

        Validate.notNullOrBlank(voterID, "Voter ID");
        Validate.notNullOrBlank(electionEventID, "Election event ID");
        Validate.notNull(exponentiatedGenerator,
            "Exponentiated generator element");
        Validate.notNull(witness, "Witness");
    }

    private void validateExponentiationProofGeneratorInput(
            final List<ZpGroupElement> exponentiatedElements,
            final List<ZpGroupElement> baseElements, final Witness witness)
            throws GeneralCryptoLibException {

        Validate.notNullOrEmptyAndNoNulls(exponentiatedElements,
            "List of exponentiated elements");
        Validate.notNullOrEmptyAndNoNulls(baseElements,
            "List of base elements");
        Validate.notNull(witness, "Witness");
        Validate.equals(exponentiatedElements.size(), baseElements.size(),
            "Number of exponentiated elements", "number of base elements");
    }

    private void validateDecryptionProofGeneratorInput(
            final ElGamalPublicKey publicKey, final Ciphertext ciphertext,
            final List<ZpGroupElement> plaintext,
            final ElGamalPrivateKey privateKey)
            throws GeneralCryptoLibException {

        Validate.notNull(publicKey, "ElGamal public key");
        Validate.notNull(ciphertext, "Ciphertext");
        Validate.notNullOrEmptyAndNoNulls(plaintext, "Plaintext");
        Validate.notNull(privateKey, "ElGamal private key");
        int publicKeyLength = publicKey.getKeys().size();
        int privateKeyLength = privateKey.getKeys().size();
        int ciphertextLength = ciphertext.size();
        int plaintextLength = plaintext.size();
        Validate.equals(publicKeyLength, privateKeyLength,
            "ElGamal public key length", "ElGamal private key length");
        Validate.equals(ciphertextLength, publicKeyLength + 1,
            "Ciphertext length", "ElGamal public key length plus 1");
        Validate.equals(plaintextLength, publicKeyLength,
            "Plaintext length", "ElGamal public key length");
    }

    private void validatePlaintextProofGeneratorInput(
            final ElGamalPublicKey publicKey, final Ciphertext ciphertext,
            final List<ZpGroupElement> plaintext, final Witness witness)
            throws GeneralCryptoLibException {

        Validate.notNull(publicKey, "ElGamal public key");
        Validate.notNull(ciphertext, "Ciphertext");
        Validate.notNullOrEmptyAndNoNulls(plaintext, "Plaintext");
        Validate.notNull(witness, "Witness");
        int publicKeyLength = publicKey.getKeys().size();
        int ciphertextLength = ciphertext.size();
        int plaintextLength = plaintext.size();
        Validate.equals(ciphertextLength, publicKeyLength + 1,
            "Ciphertext length", "ElGamal public key length plus 1");
        Validate.equals(plaintextLength, publicKeyLength,
            "Plaintext length", "ElGamal public key length");
    }

    private void validateSimplePlaintextEqualityProofGeneratorInput(
            final Ciphertext primaryCiphertext,
            final ElGamalPublicKey primaryPublicKey, final Witness witness,
            final Ciphertext secondaryCiphertext,
            final ElGamalPublicKey secondaryPublicKey)
            throws GeneralCryptoLibException {

        Validate.notNull(primaryCiphertext, "Primary ciphertext");
        Validate.notNull(primaryPublicKey, "Primary ElGamal public key");
        Validate.notNull(witness, "Witness");
        Validate.notNull(secondaryCiphertext, "Secondary ciphertext");
        Validate.notNull(secondaryPublicKey,
            "Secondary ElGamal public key");
        int primaryCiphertextLength = primaryCiphertext.size();
        int primaryPublicKeyLength = primaryPublicKey.getKeys().size();
        int secondaryCiphertextLength = secondaryCiphertext.size();
        int secondaryPublicKeyLength = secondaryPublicKey.getKeys().size();
        Validate.equals(primaryCiphertextLength, secondaryCiphertextLength,
            "Primary ciphertext length", "secondary ciphertext length");
        Validate.equals(primaryPublicKeyLength, secondaryPublicKeyLength,
            "Primary ElGamal public key length",
            "secondary ElGamal public key length");
        Validate.equals(primaryCiphertextLength,
            primaryPublicKeyLength + 1, "Ciphertext length",
            "ElGamal public key length plus 1");
    }

    private void validatePlaintextEqualityProofGeneratorInput(
            final Ciphertext primaryCiphertext,
            final ElGamalPublicKey primaryPublicKey,
            final Witness primaryWitness,
            final Ciphertext secondaryCiphertext,
            final ElGamalPublicKey secondaryPublicKey,
            final Witness secondaryWitness)
            throws GeneralCryptoLibException {

        Validate.notNull(primaryCiphertext, "Primary ciphertext");
        Validate.notNull(primaryPublicKey, "Primary ElGamal public key");
        Validate.notNull(primaryWitness, "Primary witness");
        Validate.notNull(secondaryCiphertext, "Secondary ciphertext");
        Validate.notNull(secondaryPublicKey,
            "Secondary ElGamal public key");
        Validate.notNull(secondaryWitness, "Secondary witness");
        int primaryCiphertextLength = primaryCiphertext.size();
        int primaryPublicKeyLength = primaryPublicKey.getKeys().size();
        int secondaryCiphertextLength = secondaryCiphertext.size();
        int secondaryPublicKeyLength = secondaryPublicKey.getKeys().size();
        Validate.equals(primaryCiphertextLength, secondaryCiphertextLength,
            "Primary ciphertext length", "secondary ciphertext length");
        Validate.equals(primaryPublicKeyLength, secondaryPublicKeyLength,
            "Primary ElGamal public key length",
            "secondary ElGamal public key length");
        Validate.equals(primaryCiphertextLength,
            primaryPublicKeyLength + 1, "Ciphertext length",
            "ElGamal public key length plus 1");
    }

    private void validatePlaintextExponentEqualityProofGeneratorInput(
            final Ciphertext ciphertext,
            final List<ZpGroupElement> baseElements,
            final Witness witness1, final Witness witness2)
            throws GeneralCryptoLibException {

        Validate.notNull(ciphertext, "Ciphertext");
        Validate.notNullOrEmptyAndNoNulls(baseElements,
            "List of base elements");
        Validate.notNull(witness1, "First witness");
        Validate.notNull(witness2, "Second witness");
        int expectedNumBaseElements = (2 * ciphertext.size()) - 1;
        Validate.equals(baseElements.size(), expectedNumBaseElements,
            "Length of list of base elements",
            "twice the ciphertext length minus 1");
    }
}
