/*
 * 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.elgamal.service;

import static junitparams.JUnitParamsRunner.$;

import java.math.BigInteger;
import java.security.Security;
import java.util.ArrayList;
import java.util.List;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.AfterClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;

import com.scytl.cryptolib.api.exceptions.CryptoLibException;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.elgamal.bean.ElGamalComputationsValues;
import com.scytl.cryptolib.elgamal.bean.ElGamalEncrypterValues;
import com.scytl.cryptolib.elgamal.bean.ElGamalEncryptionParameters;
import com.scytl.cryptolib.elgamal.bean.ElGamalKeyPair;
import com.scytl.cryptolib.elgamal.bean.ElGamalPrivateKey;
import com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey;
import com.scytl.cryptolib.elgamal.cryptoapi.CryptoAPIElGamalEncrypter;
import com.scytl.cryptolib.elgamal.cryptoapi.CryptoAPIElGamalKeyPairGenerator;
import com.scytl.cryptolib.elgamal.cryptoapi.CryptoAPIElGamalReEncrypter;
import com.scytl.cryptolib.elgamal.utils.ElGamalTestDataGenerator;
import com.scytl.cryptolib.ffc.cryptoapi.FFCDomainParameters;
import com.scytl.cryptolib.ffc.cryptoapi.FFCPrime;
import com.scytl.cryptolib.ffc.cryptoapi.FFCPrimes;
import com.scytl.cryptolib.mathematical.groups.impl.ZpGroupElement;
import com.scytl.cryptolib.mathematical.groups.impl.ZpSubgroup;
import com.scytl.cryptolib.mathematical.groups.utils.MathematicalTestDataGenerator;
import com.scytl.cryptolib.primitives.securerandom.constants.SecureRandomConstants;
import com.scytl.cryptolib.test.tools.utils.CommonTestDataGenerator;

import junitparams.JUnitParamsRunner;
import junitparams.Parameters;

/**
 * Tests of ElGamal service API input validation.
 */
@RunWith(JUnitParamsRunner.class)
public class ElGamalServiceValidationTest {

    private static final int MIN_NUM_TEST_ELEMENTS = 3;

    private static final int MAX_NUM_TEST_ELEMENTS = 10;

    private static ElGamalService _elGamalServiceForDefaultPolicy;

    private static String _whiteSpaceString;

    private static ElGamalEncryptionParameters _encryptionParameters;

    private static int _numElements;

    private static CryptoAPIElGamalKeyPairGenerator _keyPairGenerator;

    private static ElGamalPublicKey _publicKey;

    private static ElGamalPrivateKey _privateKey;

    private static CryptoAPIElGamalEncrypter _encrypter;

    private static List<ZpGroupElement> _plaintext;

    private static List<String> _plaintextAsStrings;

    private static ElGamalEncrypterValues _preComputedValues;

    private static CryptoAPIElGamalEncrypter _encrypterForShortExponents;

    private static CryptoAPIElGamalReEncrypter _reEncrypter;

    private static int _numTooManyElements;

    private static ElGamalPublicKey _publicKeyWithInvalidGroupElements;

    private static ElGamalPrivateKey _privateKeyWithInvalidGroupElements;

    private static List<ZpGroupElement> _emptyPlaintext;

    private static List<ZpGroupElement> _plaintextWithNullElement;

    private static List<ZpGroupElement> _plaintextWithTooManyElements;

    private static List<String> _emptyPlaintextAsStrings;

    private static List<String> _plaintextAsStringsWithNullElement;

    private static List<String> _plaintextAsStringsWithEmptyElement;

    private static List<String> _plaintextAsStringsWithWhiteSpaceElement;

    private static List<String> _plaintextAsStringsWithTooManyElements;

    private static List<ZpGroupElement> _plaintextForShortExponentsWithNullElement;

    private static List<ZpGroupElement> _plaintextForShortExponentsWithTooManyElements;

    private static ElGamalComputationsValues _ciphertextWithTooManyElements;

    private static ElGamalComputationsValues _ciphertextWithInvalidGroupElement;

    static {
        try {
            setUp();
        } catch (GeneralCryptoLibException e) {
            throw new CryptoLibException("Setup failed for class "
                + ElGamalServiceValidationTest.class.getSimpleName());
        }
    }

    public static void setUp() throws GeneralCryptoLibException {

        Security.addProvider(new BouncyCastleProvider());

        _elGamalServiceForDefaultPolicy = new ElGamalService();

        _whiteSpaceString = CommonTestDataGenerator
            .getWhiteSpaceString(CommonTestDataGenerator.getInt(1,
                SecureRandomConstants.MAXIMUM_GENERATED_STRING_LENGTH));

        ZpSubgroup zpSubgroup =
            MathematicalTestDataGenerator.getZpSubgroup();

        _encryptionParameters = ElGamalTestDataGenerator
            .getElGamalEncryptionParameters(zpSubgroup);

        _numElements = CommonTestDataGenerator
            .getInt(MIN_NUM_TEST_ELEMENTS, MAX_NUM_TEST_ELEMENTS);

        _keyPairGenerator =
            _elGamalServiceForDefaultPolicy.getElGamalKeyPairGenerator();

        ElGamalKeyPair elGamalKeyPair = _keyPairGenerator
            .generateKeys(_encryptionParameters, _numElements);
        _publicKey = elGamalKeyPair.getPublicKeys();
        _privateKey = elGamalKeyPair.getPrivateKeys();

        _encrypter =
            _elGamalServiceForDefaultPolicy.createEncrypter(_publicKey);

        _plaintext = MathematicalTestDataGenerator
            .getZpGroupElements(zpSubgroup, _numElements);
        _plaintextAsStrings = MathematicalTestDataGenerator
            .zpGroupElementsToStrings(_plaintext);

        _preComputedValues = _encrypter.preCompute();

        ZpSubgroup qrSubgroup =
            MathematicalTestDataGenerator.getQrSubgroup();
        ElGamalEncryptionParameters encryptionParametersQr =
            ElGamalTestDataGenerator
                .getElGamalEncryptionParameters(qrSubgroup);
        ElGamalKeyPair elGamalKeyPairQr = _keyPairGenerator
            .generateKeys(encryptionParametersQr, _numElements);
        ElGamalPublicKey publicKeyQr = elGamalKeyPairQr.getPublicKeys();
        ElGamalPrivateKey privateKeyQr = elGamalKeyPairQr.getPrivateKeys();

        _encrypterForShortExponents = ElGamalTestDataGenerator
            .getEncrypterForShortExponents(publicKeyQr);

        _reEncrypter =
            _elGamalServiceForDefaultPolicy.createReEncrypter(_publicKey);

        _numTooManyElements = _numElements + 1;

        _publicKeyWithInvalidGroupElements =
            new ElGamalPublicKey(publicKeyQr.getKeys(), zpSubgroup);
        _privateKeyWithInvalidGroupElements =
            new ElGamalPrivateKey(privateKeyQr.getKeys(), zpSubgroup);

        _emptyPlaintext = new ArrayList<>();
        _plaintextWithNullElement = new ArrayList<>(_plaintext);
        _plaintextWithNullElement.set(0, null);
        _plaintextWithTooManyElements = MathematicalTestDataGenerator
            .getZpGroupElements(zpSubgroup, _numElements + 1);

        _emptyPlaintextAsStrings = new ArrayList<>();
        _plaintextAsStringsWithNullElement =
            new ArrayList<>(_plaintextAsStrings);
        _plaintextAsStringsWithNullElement.set(0, null);
        _plaintextAsStringsWithEmptyElement =
            new ArrayList<>(_plaintextAsStrings);
        _plaintextAsStringsWithEmptyElement.set(0, "");
        _plaintextAsStringsWithWhiteSpaceElement =
            new ArrayList<>(_plaintextAsStrings);
        _plaintextAsStringsWithWhiteSpaceElement.set(0, _whiteSpaceString);
        _plaintextAsStringsWithTooManyElements =
            MathematicalTestDataGenerator
                .zpGroupElementsToStrings(_plaintextWithTooManyElements);

        _plaintextForShortExponentsWithNullElement =
            new ArrayList<>(_plaintext);
        _plaintextForShortExponentsWithNullElement.set(0, null);
        _plaintextForShortExponentsWithTooManyElements =
            MathematicalTestDataGenerator.getZpGroupElements(qrSubgroup,
                _numElements + 1);

        elGamalKeyPair = _keyPairGenerator
            .generateKeys(_encryptionParameters, _numElements + 1);
        CryptoAPIElGamalEncrypter encrypter =
            _elGamalServiceForDefaultPolicy
                .createEncrypter(elGamalKeyPair.getPublicKeys());
        _ciphertextWithTooManyElements =
            encrypter.encryptGroupElements(_plaintextWithTooManyElements)
                .getComputationValues();
        List<ZpGroupElement> plaintextForShortExponents =
            MathematicalTestDataGenerator.getZpGroupElements(qrSubgroup,
                _numElements);
        _ciphertextWithInvalidGroupElement =
            new ElGamalComputationsValues(plaintextForShortExponents);
    }

    @AfterClass
    public static void tearDown() {

        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
    }

    @Rule
    public ExpectedException thrown = ExpectedException.none();

    @Test
    @Parameters(method = "createElGamalService")
    public void testElGamalServiceCreationValidation(String path,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        new ElGamalService(path);
    }

    public static Object[] createElGamalService() {

        return $($(null, "Properties file path is null."),
            $("", "Properties file path is blank."),
            $(_whiteSpaceString, "Properties file path is blank."));
    }

    @Test
    @Parameters(method = "createElGamalEncrypter")
    public void testElGamalEncrypterCreationValidation(
            ElGamalPublicKey publicKey, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _elGamalServiceForDefaultPolicy.createEncrypter(publicKey);
    }

    public static Object[] createElGamalEncrypter() {

        return $($(null, "ElGamal public key is null."), $(
            _publicKeyWithInvalidGroupElements,
            " is not a member of the public key's mathematical group."));
    }

    @Test
    @Parameters(method = "createElGamalDecrypter")
    public void testElGamalDecrypterCreationValidation(
            ElGamalPrivateKey privateKey, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _elGamalServiceForDefaultPolicy.createDecrypter(privateKey);
    }

    public static Object[] createElGamalDecrypter() {

        return $($(null, "ElGamal private key is null."), $(
            _privateKeyWithInvalidGroupElements,
            " is not an exponent of the private key's mathematical group."));
    }

    @Test
    @Parameters(method = "createElGamalReEncrypter")
    public void testElGamalReEncrypterCreationValidation(
            ElGamalPublicKey publicKey, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _elGamalServiceForDefaultPolicy.createReEncrypter(publicKey);
    }

    public static Object[] createElGamalReEncrypter() {

        return $($(null, "ElGamal public key is null."), $(
            _publicKeyWithInvalidGroupElements,
            " is not a member of the public key's mathematical group."));
    }

    @Test
    @Parameters(method = "generateKeyPair")
    public void testKeyPairGenerationValidation(
            ElGamalEncryptionParameters encryptionParameters, int length,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _keyPairGenerator.generateKeys(encryptionParameters, length);
    }

    public static Object[] generateKeyPair() {

        return $(
            $(null, _numElements,
                "Mathematical group parameters object is null."),
            $(_encryptionParameters, 0,
                "Length of ElGamal key pair to generate must be a positive integer; Found 0"));
    }

    @Test
    @Parameters(method = "encryptGroupElements")
    public void testGroupElementsEncryptionValidation(
            List<ZpGroupElement> messages, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _encrypter.encryptGroupElements(messages);
    }

    public static Object[] encryptGroupElements() {

        return $(
            $(null,
                "List of mathematical group elements to ElGamal encrypt is null."),
            $(_emptyPlaintext,
                "List of mathematical group elements to ElGamal encrypt is empty."),
            $(_plaintextWithNullElement,
                "List of mathematical group elements to ElGamal encrypt contains one or more null elements."),
            $(_plaintextWithTooManyElements,
                "Number of mathematical group elements to ElGamal encrypt must be less than or equal to encrypter public key length: "
                    + _numElements + "; Found " + _numTooManyElements));
    }

    @Test
    @Parameters(method = "encryptGroupElementsUsingPreComputedValues")
    public void testGroupElementsEncryptionUsingPreComputedValuesValidation(
            List<ZpGroupElement> messages,
            ElGamalEncrypterValues preComputedValues, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _encrypter.encryptGroupElements(messages, preComputedValues);
    }

    public static Object[] encryptGroupElementsUsingPreComputedValues() {

        return $(
            $(null, _preComputedValues,
                "List of mathematical group elements to ElGamal encrypt is null."),
            $(_emptyPlaintext, _preComputedValues,
                "List of mathematical group elements to ElGamal encrypt is empty."),
            $(_plaintextWithNullElement, _preComputedValues,
                "List of mathematical group elements to ElGamal encrypt contains one or more null elements."),
            $(_plaintextWithTooManyElements, _preComputedValues,
                "Number of mathematical group elements to ElGamal encrypt must be less than or equal to encrypter public key length: "
                    + _numElements + "; Found " + _numTooManyElements),
            $(_plaintext, null,
                "ElGamal pre-computed values object is null."));
    }

    @Test
    @Parameters(method = "encryptStrings")
    public void testStringsEncryptionValidation(List<String> messages,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _encrypter.encryptStrings(messages);
    }

    public static Object[] encryptStrings() {

        return $(
            $(null,
                "List of stringified mathematical group elements to ElGamal encrypt is null."),
            $(_emptyPlaintextAsStrings,
                "List of stringified mathematical group elements to ElGamal encrypt is empty."),
            $(_plaintextAsStringsWithNullElement,
                "A stringified mathematical group element to ElGamal encrypt is null."),
            $(_plaintextAsStringsWithEmptyElement,
                "A stringified mathematical group element to ElGamal encrypt is blank."),
            $(_plaintextAsStringsWithWhiteSpaceElement,
                "A stringified mathematical group element to ElGamal encrypt is blank."),
            $(_plaintextAsStringsWithTooManyElements,
                "Number of mathematical group elements to ElGamal encrypt must be less than or equal to encrypter public key length: "
                    + _numElements + "; Found " + _numTooManyElements));
    }

    @Test
    @Parameters(method = "encryptStringsUsingPreComputedValues")
    public void testStringsEncryptionUsingPreComputedValuesValidation(
            List<String> messages,
            ElGamalEncrypterValues preComputedValues, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _encrypter.encryptStrings(messages, preComputedValues);
    }

    public static Object[] encryptStringsUsingPreComputedValues() {

        return $(
            $(null, _preComputedValues,
                "List of stringified mathematical group elements to ElGamal encrypt is null."),
            $(_emptyPlaintextAsStrings, _preComputedValues,
                "List of stringified mathematical group elements to ElGamal encrypt is empty."),
            $(_plaintextAsStringsWithNullElement, _preComputedValues,
                "A stringified mathematical group element to ElGamal encrypt is null."),
            $(_plaintextAsStringsWithEmptyElement, _preComputedValues,
                "A stringified mathematical group element to ElGamal encrypt is blank."),
            $(_plaintextAsStringsWithWhiteSpaceElement, _preComputedValues,
                "A stringified mathematical group element to ElGamal encrypt is blank."),
            $(_plaintextAsStringsWithTooManyElements, _preComputedValues,
                "Number of mathematical group elements to ElGamal encrypt must be less than or equal to encrypter public key length: "
                    + _numElements + "; Found " + _numTooManyElements),
            $(_plaintextAsStrings, null,
                "ElGamal pre-computed values object is null."));
    }

    @Test
    @Parameters(method = "encryptGroupElementsUsingShortExponent")
    public void testGroupElementsEncryptionUsingShortExponentValidation(
            List<ZpGroupElement> messages, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _encrypterForShortExponents
            .encryptGroupElementsWithShortExponent(messages);
    }

    public static Object[] encryptGroupElementsUsingShortExponent() {

        return $(
            $(null,
                "List of mathematical group elements to ElGamal encrypt is null."),
            $(_emptyPlaintext,
                "List of mathematical group elements to ElGamal encrypt is empty."),
            $(_plaintextForShortExponentsWithNullElement,
                "List of mathematical group elements to ElGamal encrypt contains one or more null elements."),
            $(_plaintextForShortExponentsWithTooManyElements,
                "Number of mathematical group elements to ElGamal encrypt must be less than or equal to encrypter public key length: "
                    + _numElements + "; Found " + _numTooManyElements));
    }

    @Test
    @Parameters(method = "encryptGroupElementsUsingShortExponentAndPreComputedValues")
    public void testGroupElementsEncryptionUsingShortExponentAndPreComputedValuesValidation(
            List<ZpGroupElement> messages,
            ElGamalEncrypterValues preComputedValues, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _encrypterForShortExponents.encryptGroupElements(messages,
            preComputedValues);
    }

    public static Object[] encryptGroupElementsUsingShortExponentAndPreComputedValues() {

        ElGamalEncrypterValues preComputedValuesFromShortExponents =
            _encrypterForShortExponents.preCompute();

        return $(
            $(null, preComputedValuesFromShortExponents,
                "List of mathematical group elements to ElGamal encrypt is null."),
            $(_emptyPlaintext, preComputedValuesFromShortExponents,
                "List of mathematical group elements to ElGamal encrypt is empty."),
            $(_plaintextForShortExponentsWithNullElement,
                preComputedValuesFromShortExponents,
                "List of mathematical group elements to ElGamal encrypt contains one or more null elements."),
            $(_plaintextForShortExponentsWithTooManyElements,
                preComputedValuesFromShortExponents,
                "Number of mathematical group elements to ElGamal encrypt must be less than or equal to encrypter public key length: "
                    + _numElements + "; Found " + _numTooManyElements));
    }

    @Test
    @Parameters(method = "decryptCiphertext")
    public void testCiphertextDecryptionValidation(
            ElGamalComputationsValues ciphertext, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _elGamalServiceForDefaultPolicy.createDecrypter(_privateKey)
            .decrypt(ciphertext, true);
    }

    public static Object[] decryptCiphertext() {

        return $($(null, "ElGamal ciphertext is null."),
            $(_ciphertextWithTooManyElements,
                "ElGamal ciphertext length must be less than or equal to decrypter private key length: "
                    + _numElements + "; Found " + _numTooManyElements),
            $(_ciphertextWithInvalidGroupElement,
                "ElGamal ciphertext contains one or more elements that do not belong to mathematical group of decrypter private key."));
    }

    @Test
    @Parameters(method = "reEncryptGroupElements")
    public void testGroupElementsReEncryptionValidation(
            ElGamalComputationsValues ciphertext, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _reEncrypter.reEncryptGroupElements(ciphertext);
    }

    public static Object[] reEncryptGroupElements() {

        return $($(null, "ElGamal ciphertext is null."),
            $(_ciphertextWithTooManyElements,
                "ElGamal ciphertext length must be less than or equal to re-encrypter public key length: "
                    + _numElements + "; Found " + _numTooManyElements));
    }

    @Test
    @Parameters(method = "reEncryptGroupElementsUsingPreComputedValues")
    public void testGroupElementsReEncryptionUsingPreComputedValuesValidation(
            ElGamalComputationsValues ciphertext,
            ElGamalEncrypterValues preComputedValues, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _reEncrypter.reEncryptGroupElements(ciphertext, preComputedValues);
    }

    public static Object[] reEncryptGroupElementsUsingPreComputedValues()
            throws GeneralCryptoLibException {

        ElGamalComputationsValues reEncryptedPlaintext = _reEncrypter
            .reEncryptGroupElements(_encrypter
                .encryptGroupElements(_plaintext).getComputationValues())
            .getComputationValues();
        ElGamalEncrypterValues preComputedValuesForReEncryption =
            _reEncrypter.preCompute();

        return $(
            $(null, preComputedValuesForReEncryption,
                "ElGamal ciphertext is null."),
            $(_ciphertextWithTooManyElements,
                preComputedValuesForReEncryption,
                "ElGamal ciphertext length must be less than or equal to re-encrypter public key length: "
                    + _numElements + "; Found " + _numTooManyElements),
            $(reEncryptedPlaintext, null,
                "ElGamal pre-computed values object is null."));
    }

    @Test
    @Parameters(method = "deserializeElGamalEncryptionParameters")
    public void testElGamalEncryptionParametersDeserializationValidation(
            String jsonStr, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        ElGamalEncryptionParameters.fromJson(jsonStr);
    }

    public static Object[] deserializeElGamalEncryptionParameters()
            throws GeneralCryptoLibException, NoSuchFieldException,
            SecurityException, IllegalArgumentException,
            IllegalAccessException {

        String encryptionParamsWithNullP =
            "{\"encryptionParams\":{\"g\":\"Ag==\",\"p\":null,\"q\":\"Cw==\"}}";
        String encryptionParamsWithNullQ =
            "{\"encryptionParams\":{\"g\":\"Ag==\",\"p\":\"Fw==\",\"q\":null}}";
        String encryptionParamsWithNullG =
            "{\"encryptionParams\":{\"g\":null,\"p\":\"Fw==\",\"q\":\"Cw==\"}}";
        String encryptionParamsWithIncorrectPQRelationship =
            "{\"encryptionParams\":{\"g\":\"Ag==\",\"q\":\"Fw==\",\"p\":\"Cw==\"}}";
        String encryptionParamsWithIncorrectPGRelationship =
            "{\"encryptionParams\":{\"g\":\"Fw==\",\"p\":\"Fw==\",\"q\":\"Cw==\"}}";

        return $(
            $(null,
                ElGamalEncryptionParameters.class.getSimpleName()
                    + " JSON string is null."),
            $("",
                ElGamalEncryptionParameters.class.getSimpleName()
                    + " JSON string is blank."),
            $(_whiteSpaceString,
                ElGamalEncryptionParameters.class.getSimpleName()
                    + " JSON string is blank."),
            $(encryptionParamsWithNullP,
                "Zp subgroup p parameter is null."),
            $(encryptionParamsWithNullQ,
                "Zp subgroup q parameter is null."),
            $(encryptionParamsWithNullG, "Zp subgroup generator is null."),
            $(encryptionParamsWithIncorrectPQRelationship,
                "Zp subgroup q parameter must be less than or equal to Zp subgroup p parameter minus 1"),
            $(encryptionParamsWithIncorrectPGRelationship,
                "Zp subgroup generator must be less than or equal to Zp subgroup p parameter minus 1"));
    }

    @Test
    @Parameters(method = "deserializeElGamalPublicKey")
    public void testElGamalPublicKeyDeserializationValidation(
            String jsonStr, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        ElGamalPublicKey.fromJson(jsonStr);
    }

    public static Object[] deserializeElGamalPublicKey() {

        String publicKeyWithNullZpSubgroup =
            "{\"publicKey\":{\"zpSubgroup\":null,\"elements\":[\"Ag==\",\"Ag==\"]}}";
        String publicKeyWithNullElementList =
            "{\"publicKey\":{\"zpSubgroup\":{\"p\":\"Fw==\",\"q\":\"Cw==\",\"g\":\"Ag==\"},\"elements\":null}}";
        String publicKeyWithEmptyElementList =
            "{\"publicKey\":{\"zpSubgroup\":{\"p\":\"Fw==\",\"q\":\"Cw==\",\"g\":\"Ag==\"},\"elements\":[]}}";
        String publicKeyWithNullInElementList =
            "{\"publicKey\":{\"zpSubgroup\":{\"p\":\"Fw==\",\"q\":\"Cw==\",\"g\":\"Ag==\"},\"elements\":[\"Ag==\",null]}}";

        return $(
            $(null,
                ElGamalPublicKey.class.getSimpleName()
                    + " JSON string is null."),
            $("",
                ElGamalPublicKey.class.getSimpleName()
                    + " JSON string is blank."),
            $(_whiteSpaceString,
                ElGamalPublicKey.class.getSimpleName()
                    + " JSON string is blank."),
            $(publicKeyWithNullZpSubgroup, "Zp subgroup is null."),
            $(publicKeyWithNullElementList,
                "List of ElGamal public key elements is null."),
            $(publicKeyWithEmptyElementList,
                "List of ElGamal public key elements is empty"),
            $(publicKeyWithNullInElementList,
                "List of ElGamal public key elements contains one or more null elements."));
    }

    @Test
    @Parameters(method = "deserializeElGamalPrivateKey")
    public void testElGamalPrivateKeyDeserializationValidation(
            String jsonStr, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        ElGamalPrivateKey.fromJson(jsonStr);
    }

    public static Object[] deserializeElGamalPrivateKey() {

        String privateKeyWithNullZpSubgroup =
            "{\"privateKey\":{\"zpSubgroup\":null,\"exponents\":[\"BA==\",\"BQ==\"]}}";
        String privateKeyWithNullElementList =
            "{\"privateKey\":{\"zpSubgroup\":{\"p\":\"Fw==\",\"q\":\"Cw==\",\"g\":\"Ag==\"},\"exponents\":null}}";
        String privateKeyWithEmptyElementList =
            "{\"privateKey\":{\"zpSubgroup\":{\"p\":\"Fw==\",\"q\":\"Cw==\",\"g\":\"Ag==\"},\"exponents\":[]}}";
        String privateKeyWithNullInElementList =
            "{\"privateKey\":{\"zpSubgroup\":{\"p\":\"Fw==\",\"q\":\"Cw==\",\"g\":\"Ag==\"},\"exponents\":[\"BA==\",null]}}";

        return $(
            $(null,
                ElGamalPrivateKey.class.getSimpleName()
                    + " JSON string is null."),
            $("",
                ElGamalPrivateKey.class.getSimpleName()
                    + " JSON string is blank."),
            $(_whiteSpaceString,
                ElGamalPrivateKey.class.getSimpleName()
                    + " JSON string is blank."),
            $(privateKeyWithNullZpSubgroup, "Zp subgroup is null."),
            $(privateKeyWithNullElementList,
                "List of ElGamal private key exponents is null."),
            $(privateKeyWithEmptyElementList,
                "List of ElGamal private key exponents is empty"),
            $(privateKeyWithNullInElementList,
                "List of ElGamal private key exponents contains one or more null elements."));
    }

    @Test
    @Parameters(method = "deserializeElGamalCiphertext")
    public void testElGamalCiphertextDeserializationValidation(
            String jsonStr, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        ElGamalComputationsValues.fromJson(jsonStr);
    }

    @Test(expected = GeneralCryptoLibException.class)
    public void testGenerateEncryptionParametersFFCDomainParametersNull()
            throws GeneralCryptoLibException {
        _elGamalServiceForDefaultPolicy.generateEncryptionParameters(null);
    }

    @Test(expected = GeneralCryptoLibException.class)
    public void testGenerateEncryptionParametersFFCDomainParametersInvalid()
            throws GeneralCryptoLibException {
        FFCPrime p =
            new FFCPrime(BigInteger.valueOf(23), BigInteger.ZERO, 1);
        FFCPrime q =
            new FFCPrime(BigInteger.valueOf(11), BigInteger.ZERO, 1);
        FFCPrimes primes = new FFCPrimes(p, q, BigInteger.ZERO);
        BigInteger g = p.value();

        _elGamalServiceForDefaultPolicy.generateEncryptionParameters(
            new FFCDomainParameters(primes, g));
    }

    public static Object[] deserializeElGamalCiphertext() {

        String ciphertextWithNullGamma =
            "{\"ciphertext\":{\"gamma\":null,\"phis\":[\"AQ==\",\"Ag==\"],\"p\":\"Fw==\",\"q\":\"Cw==\"}}";
        String ciphertextWithNullPhiList =
            "{\"ciphertext\":{\"gamma\":\"AQ==\",\"phis\":null,\"p\":\"Fw==\",\"q\":\"Cw==\"}}";
        String ciphertextWithEmptyPhiList =
            "{\"ciphertext\":{\"gamma\":\"AQ==\",\"phis\":[],\"p\":\"Fw==\",\"q\":\"Cw==\"}}";
        String ciphertextWithNullInPhiList =
            "{\"ciphertext\":{\"gamma\":\"AQ==\",\"phis\":[\"AQ==\",null],\"p\":\"Fw==\",\"q\":\"Cw==\"}}";
        String ciphertextWithNullP =
            "{\"ciphertext\":{\"gamma\":\"AQ==\",\"phis\":[\"AQ==\",\"Ag==\"],\"p\":null,\"q\":\"Cw==\"}}";
        String ciphertextWithNullQ =
            "{\"ciphertext\":{\"gamma\":\"AQ==\",\"phis\":[\"AQ==\",\"Ag==\"],\"p\":\"Fw==\",\"q\":null}}";

        return $(
            $(null,
                ElGamalComputationsValues.class.getSimpleName()
                    + " JSON string is null."),
            $("",
                ElGamalComputationsValues.class.getSimpleName()
                    + " JSON string is blank."),
            $(_whiteSpaceString,
                ElGamalComputationsValues.class.getSimpleName()
                    + " JSON string is blank."),
            $(ciphertextWithNullGamma, "ElGamal gamma element is null."),
            $(ciphertextWithNullPhiList,
                "List of ElGamal phi elements is null."),
            $(ciphertextWithEmptyPhiList,
                "List of ElGamal phi elements is empty."),
            $(ciphertextWithNullInPhiList,
                "Zp group element value is null."),
            $(ciphertextWithNullP, "Zp subgroup p parameter is null."),
            $(ciphertextWithNullQ, "Zp subgroup q parameter is null."));
    }
}
