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

import static junitparams.JUnitParamsRunner.$;

import java.security.Security;
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.ElGamalEncrypterValues;
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.elgamal.utils.ElGamalTestDataGenerator;
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.proofs.bean.ProofPreComputedValues;
import com.scytl.cryptolib.proofs.proof.Proof;

import junitparams.JUnitParamsRunner;
import junitparams.Parameters;

/**
 * Tests of Proofs service API input validation for plaintext equality zero
 * knowledge proofs of knowledge.
 */
@RunWith(JUnitParamsRunner.class)
public class PlaintextEqualityProofValidationTest {

    private static final int NUM_PLAINTEXT_ELEMENTS = 6;

    private static ProofsService _proofsServiceForDefaultPolicy;

    private static ZpSubgroup _zpSubgroup;

    private static List<ZpGroupElement> _plaintext;

    private static ElGamalPublicKey _primaryPublicKey;

    private static ElGamalPublicKey _secondaryPublicKey;

    private static Ciphertext _primaryCiphertext;

    private static Witness _primaryWitness;

    private static Ciphertext _secondaryCiphertext;

    private static Witness _secondaryWitness;

    private static ElGamalPublicKey _shorterPrimaryPublicKey;

    private static ElGamalPublicKey _shorterSecondaryPublicKey;

    private static Ciphertext _shorterPrimaryCiphertext;

    private static int _publicKeyLength;

    private static int _ciphertextLength;

    private static int _shorterPublicKeyLength;

    private static int _shorterCiphertextLength;

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

    public static void setUp() throws GeneralCryptoLibException {
        Security.addProvider(new BouncyCastleProvider());

        Security.addProvider(new BouncyCastleProvider());

        _proofsServiceForDefaultPolicy = new ProofsService();

        _zpSubgroup = MathematicalTestDataGenerator.getZpSubgroup();

        _primaryPublicKey = ElGamalTestDataGenerator
            .getKeyPair(_zpSubgroup, NUM_PLAINTEXT_ELEMENTS)
            .getPublicKeys();
        _secondaryPublicKey = ElGamalTestDataGenerator
            .getKeyPair(_zpSubgroup, NUM_PLAINTEXT_ELEMENTS)
            .getPublicKeys();

        _plaintext = MathematicalTestDataGenerator
            .getZpGroupElements(_zpSubgroup, NUM_PLAINTEXT_ELEMENTS);

        ElGamalEncrypterValues encrypterValues =
            (ElGamalEncrypterValues) ElGamalTestDataGenerator
                .encryptGroupElements(_primaryPublicKey, _plaintext);
        _primaryCiphertext = encrypterValues;
        _primaryWitness = encrypterValues;
        encrypterValues = (ElGamalEncrypterValues) ElGamalTestDataGenerator
            .encryptGroupElements(_secondaryPublicKey, _plaintext);
        _secondaryCiphertext = encrypterValues;
        _secondaryWitness = encrypterValues;

        _shorterPrimaryPublicKey = ElGamalTestDataGenerator
            .getKeyPair(_zpSubgroup, (NUM_PLAINTEXT_ELEMENTS - 1))
            .getPublicKeys();
        _shorterSecondaryPublicKey = ElGamalTestDataGenerator
            .getKeyPair(_zpSubgroup, (NUM_PLAINTEXT_ELEMENTS - 1))
            .getPublicKeys();

        List<ZpGroupElement> shorterPlaintext =
            MathematicalTestDataGenerator.getZpGroupElements(_zpSubgroup,
                (NUM_PLAINTEXT_ELEMENTS - 1));
        encrypterValues = (ElGamalEncrypterValues) ElGamalTestDataGenerator
            .encryptGroupElements(_shorterPrimaryPublicKey,
                shorterPlaintext);
        _shorterPrimaryCiphertext = encrypterValues;

        _publicKeyLength = _primaryPublicKey.getKeys().size();
        _ciphertextLength = _primaryCiphertext.size();
        _shorterPublicKeyLength =
            _shorterPrimaryPublicKey.getKeys().size();
        _shorterCiphertextLength = _shorterPrimaryCiphertext.size();
    }

    @AfterClass
    public static void tearDown() {

        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
    }

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

    @Test
    @Parameters(method = "createPlaintextEqualityProof")
    public void testPlaintextEqualityProofCreationValidation(
            ZpSubgroup group, Ciphertext primaryCiphertext,
            ElGamalPublicKey primaryPublicKey, Witness primaryWitness,
            Ciphertext secondaryCiphertext,
            ElGamalPublicKey secondaryPublicKey, Witness secondaryWitness,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _proofsServiceForDefaultPolicy.createProofProverAPI(group)
            .createPlaintextEqualityProof(primaryCiphertext,
                primaryPublicKey, primaryWitness, secondaryCiphertext,
                secondaryPublicKey, secondaryWitness);
    }

    public static Object[] createPlaintextEqualityProof() {

        return $(
            $(null, _primaryCiphertext, _primaryPublicKey, _primaryWitness,
                _secondaryCiphertext, _secondaryPublicKey,
                _secondaryWitness, "Zp subgroup is null."),
            $(_zpSubgroup, null, _primaryPublicKey, _primaryWitness,
                _secondaryCiphertext, _secondaryPublicKey,
                _secondaryWitness, "Primary ciphertext is null."),
            $(_zpSubgroup, _primaryCiphertext, null, _primaryWitness,
                _secondaryCiphertext, _secondaryPublicKey,
                _secondaryWitness, "Primary ElGamal public key is null."),
            $(_zpSubgroup, _primaryCiphertext, _primaryPublicKey, null,
                _secondaryCiphertext, _secondaryPublicKey,
                _secondaryWitness, "Primary witness is null."),
            $(_zpSubgroup, _primaryCiphertext, _primaryPublicKey,
                _primaryWitness, null, _secondaryPublicKey,
                _secondaryWitness, "Secondary ciphertext is null."),
            $(_zpSubgroup, _primaryCiphertext, _primaryPublicKey,
                _primaryWitness, _secondaryCiphertext, null,
                _secondaryWitness,
                "Secondary ElGamal public key is null."),
            $(_zpSubgroup, _primaryCiphertext, _primaryPublicKey,
                _primaryWitness, _secondaryCiphertext, _secondaryPublicKey,
                null, "Secondary witness is null."),
            $(_zpSubgroup, _shorterPrimaryCiphertext, _primaryPublicKey,
                _primaryWitness, _secondaryCiphertext, _secondaryPublicKey,
                _secondaryWitness,
                "Primary ciphertext length must be equal to secondary ciphertext length: "
                    + _ciphertextLength + "; Found "
                    + _shorterCiphertextLength),
            $(_zpSubgroup, _primaryCiphertext, _shorterPrimaryPublicKey,
                _primaryWitness, _secondaryCiphertext, _secondaryPublicKey,
                _secondaryWitness,
                "Primary ElGamal public key length must be equal to secondary ElGamal public key length: "
                    + _publicKeyLength + "; Found "
                    + _shorterPublicKeyLength),
            $(_zpSubgroup, _primaryCiphertext, _shorterPrimaryPublicKey,
                _primaryWitness, _secondaryCiphertext,
                _shorterSecondaryPublicKey, _secondaryWitness,
                "Ciphertext length must be equal to ElGamal public key length plus 1: "
                    + _shorterCiphertextLength + "; Found "
                    + _ciphertextLength));
    }

    @Test
    @Parameters(method = "preComputePlaintextEqualityProofProof")
    public void testPlaintextEqualityProofPreComputationValidation(
            ZpSubgroup group, ElGamalPublicKey primaryPublicKey,
            ElGamalPublicKey secondaryPublicKey, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _proofsServiceForDefaultPolicy.createProofPreComputerAPI(group)
            .preComputePlaintextEqualityProof(primaryPublicKey,
                secondaryPublicKey);
    }

    public static Object[] preComputePlaintextEqualityProofProof() {

        return $(
            $(null, _primaryPublicKey, _secondaryPublicKey,
                "Zp subgroup is null."),
            $(_zpSubgroup, null, _secondaryPublicKey,
                "Primary ElGamal public key is null."),
            $(_zpSubgroup, _primaryPublicKey, null,
                "Secondary ElGamal public key is null."),
            $(_zpSubgroup, _shorterPrimaryPublicKey, _secondaryPublicKey,
                "Primary ElGamal public key length must be equal to secondary ElGamal public key length: "
                    + _publicKeyLength + "; Found "
                    + _shorterPublicKeyLength));
    }

    @Test
    @Parameters(method = "createPlaintextEqualityProofUsingPreComputedValues")
    public void testPlaintextEqualityProofCreationUsingPreComputedValuesValidation(
            ZpSubgroup group, Ciphertext primaryCiphertext,
            ElGamalPublicKey primaryPublicKey, Witness primaryWitness,
            Ciphertext secondaryCiphertext,
            ElGamalPublicKey secondaryPublicKey, Witness secondaryWitness,
            ProofPreComputedValues preComputedValues, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _proofsServiceForDefaultPolicy.createProofProverAPI(group)
            .createPlaintextEqualityProof(primaryCiphertext,
                primaryPublicKey, primaryWitness, secondaryCiphertext,
                secondaryPublicKey, secondaryWitness, preComputedValues);
    }

    public static Object[] createPlaintextEqualityProofUsingPreComputedValues()
            throws GeneralCryptoLibException {

        ProofPreComputedValues preComputedValues =
            _proofsServiceForDefaultPolicy
                .createProofPreComputerAPI(_zpSubgroup)
                .preComputePlaintextEqualityProof(_primaryPublicKey,
                    _secondaryPublicKey);

        return $($(null, _primaryCiphertext, _primaryPublicKey,
            _primaryWitness, _secondaryCiphertext, _secondaryPublicKey,
            _secondaryWitness, preComputedValues, "Zp subgroup is null."),
            $(_zpSubgroup, null, _primaryPublicKey, _primaryWitness,
                _secondaryCiphertext, _secondaryPublicKey,
                _secondaryWitness, preComputedValues,
                "Primary ciphertext is null."),
            $(_zpSubgroup, _primaryCiphertext, null, _primaryWitness,
                _secondaryCiphertext, _secondaryPublicKey,
                _secondaryWitness, preComputedValues,
                "Primary ElGamal public key is null."),
            $(_zpSubgroup, _primaryCiphertext, _primaryPublicKey, null,
                _secondaryCiphertext, _secondaryPublicKey,
                _secondaryWitness, preComputedValues,
                "Primary witness is null."),
            $(_zpSubgroup, _primaryCiphertext, _primaryPublicKey,
                _primaryWitness, null, _secondaryPublicKey,
                _secondaryWitness, preComputedValues,
                "Secondary ciphertext is null."),
            $(_zpSubgroup, _primaryCiphertext, _primaryPublicKey,
                _primaryWitness, _secondaryCiphertext, null,
                _secondaryWitness, preComputedValues,
                "Secondary ElGamal public key is null."),
            $(_zpSubgroup, _primaryCiphertext, _primaryPublicKey,
                _primaryWitness, _secondaryCiphertext, _secondaryPublicKey,
                null, preComputedValues, "Secondary witness is null."),
            $(_zpSubgroup, _primaryCiphertext, _primaryPublicKey,
                _primaryWitness, _secondaryCiphertext, _secondaryPublicKey,
                _secondaryWitness, null,
                "Pre-computed values object is null."),
            $(_zpSubgroup, _shorterPrimaryCiphertext, _primaryPublicKey,
                _primaryWitness, _secondaryCiphertext, _secondaryPublicKey,
                _secondaryWitness, preComputedValues,
                "Primary ciphertext length must be equal to secondary ciphertext length: "
                    + _ciphertextLength + "; Found "
                    + _shorterCiphertextLength),
            $(_zpSubgroup, _primaryCiphertext, _shorterPrimaryPublicKey,
                _primaryWitness, _secondaryCiphertext, _secondaryPublicKey,
                _secondaryWitness, preComputedValues,
                "Primary ElGamal public key length must be equal to secondary ElGamal public key length: "
                    + _publicKeyLength + "; Found "
                    + _shorterPublicKeyLength),
            $(_zpSubgroup, _primaryCiphertext, _shorterPrimaryPublicKey,
                _primaryWitness, _secondaryCiphertext,
                _shorterSecondaryPublicKey, _secondaryWitness,
                preComputedValues,
                "Ciphertext length must be equal to ElGamal public key length plus 1: "
                    + _shorterCiphertextLength + "; Found "
                    + _ciphertextLength));
    }

    @Test
    @Parameters(method = "verifyPlaintextEqualityProof")
    public void testPlaintextEqualityProofVerificationValidation(
            ZpSubgroup group, Ciphertext primaryCiphertext,
            ElGamalPublicKey primaryPublicKey,
            Ciphertext secondaryCiphertext,
            ElGamalPublicKey secondaryPublicKey, Proof proof,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _proofsServiceForDefaultPolicy.createProofVerifierAPI(group)
            .verifyPlaintextEqualityProof(primaryCiphertext,
                primaryPublicKey, secondaryCiphertext, secondaryPublicKey,
                proof);
    }

    public static Object[] verifyPlaintextEqualityProof()
            throws GeneralCryptoLibException {

        Proof proof = _proofsServiceForDefaultPolicy
            .createProofProverAPI(_zpSubgroup)
            .createPlaintextEqualityProof(_primaryCiphertext,
                _primaryPublicKey, _primaryWitness, _secondaryCiphertext,
                _secondaryPublicKey, _secondaryWitness);

        return $(
            $(null, _primaryCiphertext, _primaryPublicKey,
                _secondaryCiphertext, _secondaryPublicKey, proof,
                "Zp subgroup is null."),
            $(_zpSubgroup, null, _primaryPublicKey, _secondaryCiphertext,
                _secondaryPublicKey, proof, "Primary ciphertext is null."),
            $(_zpSubgroup, _primaryCiphertext, null, _secondaryCiphertext,
                _secondaryPublicKey, proof,
                "Primary ElGamal public key is null."),
            $(_zpSubgroup, _primaryCiphertext, _primaryPublicKey, null,
                _secondaryPublicKey, proof,
                "Secondary ciphertext is null."),
            $(_zpSubgroup, _primaryCiphertext, _primaryPublicKey,
                _secondaryCiphertext, null, proof,
                "Secondary ElGamal public key is null."),
            $(_zpSubgroup, _primaryCiphertext, _primaryPublicKey,
                _secondaryCiphertext, _secondaryPublicKey, null,
                "Plaintext equality proof is null."),
            $(_zpSubgroup, _shorterPrimaryCiphertext, _primaryPublicKey,
                _secondaryCiphertext, _secondaryPublicKey, proof,
                "Primary ciphertext length must be equal to secondary ciphertext length: "
                    + _ciphertextLength + "; Found "
                    + _shorterCiphertextLength),
            $(_zpSubgroup, _primaryCiphertext, _shorterPrimaryPublicKey,

                _secondaryCiphertext, _secondaryPublicKey, proof,
                "Primary ElGamal public key length must be equal to secondary ElGamal public key length: "
                    + _publicKeyLength + "; Found "
                    + _shorterPublicKeyLength),
            $(_zpSubgroup, _primaryCiphertext, _shorterPrimaryPublicKey,
                _secondaryCiphertext, _shorterSecondaryPublicKey, proof,
                "Ciphertext length must be equal to ElGamal public key length plus 1: "
                    + _shorterCiphertextLength + "; Found "
                    + _ciphertextLength));
    }
}
