/*
 * 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.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.ElGamalEncrypterValues;
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.Ciphertext;
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 decryption zero knowledge
 * proofs of knowledge.
 */
@RunWith(JUnitParamsRunner.class)
public class DecryptionProofValidationTest {

    private static final int NUM_PLAINTEXT_ELEMENTS = 6;

    private static ProofsService _proofsServiceForDefaultPolicy;

    private static ZpSubgroup _zpSubgroup;

    private static ElGamalPublicKey _publicKey;

    private static ElGamalPrivateKey _privateKey;

    private static List<ZpGroupElement> _plaintext;

    private static Ciphertext _ciphertext;

    private static ZpGroupElement _gamma;

    private static List<ZpGroupElement> _emptyPlaintext;

    private static List<ZpGroupElement> _plaintextWithNullValue;

    private static ElGamalPublicKey _shorterPublicKey;

    private static List<ZpGroupElement> _shorterPlaintext;

    private static Ciphertext _shorterCiphertext;

    private static int _publicKeyLength;

    private static int _privateKeyLength;

    private static int _ciphertextLength;

    private static int _shorterPublicKeyLength;

    private static int _shorterPlaintextLength;

    private static int _shorterCiphertextLength;

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

    public static void setUp() throws GeneralCryptoLibException {

        Security.addProvider(new BouncyCastleProvider());

        _proofsServiceForDefaultPolicy = new ProofsService();

        _zpSubgroup = MathematicalTestDataGenerator.getZpSubgroup();

        ElGamalKeyPair keyPair = ElGamalTestDataGenerator
            .getKeyPair(_zpSubgroup, NUM_PLAINTEXT_ELEMENTS);
        _publicKey = keyPair.getPublicKeys();
        _privateKey = keyPair.getPrivateKeys();

        _plaintext = MathematicalTestDataGenerator
            .getZpGroupElements(_zpSubgroup, NUM_PLAINTEXT_ELEMENTS);

        ElGamalEncrypterValues preComputedEncrypterValues =
            ElGamalTestDataGenerator.preComputeEncrypterValues(_publicKey);
        _ciphertext = ElGamalTestDataGenerator.encryptGroupElements(
            _publicKey, _plaintext, preComputedEncrypterValues);
        _gamma =
            preComputedEncrypterValues.getComputationValues().getGamma();

        _emptyPlaintext = new ArrayList<>();

        _plaintextWithNullValue = new ArrayList<>(_plaintext);
        _plaintextWithNullValue.set(0, null);

        _shorterPublicKey = ElGamalTestDataGenerator
            .getKeyPair(_zpSubgroup, (NUM_PLAINTEXT_ELEMENTS - 1))
            .getPublicKeys();
        _shorterPlaintext = MathematicalTestDataGenerator
            .getZpGroupElements(_zpSubgroup, (NUM_PLAINTEXT_ELEMENTS - 1));
        _shorterCiphertext = ElGamalTestDataGenerator
            .encryptGroupElements(_shorterPublicKey, _shorterPlaintext);

        _publicKeyLength = _publicKey.getKeys().size();
        _privateKeyLength = _privateKey.getKeys().size();
        _ciphertextLength = _ciphertext.size();
        _shorterPublicKeyLength = _shorterPublicKey.getKeys().size();
        _shorterPlaintextLength = _shorterPlaintext.size();
        _shorterCiphertextLength = _shorterCiphertext.size();
    }

    @AfterClass
    public static void tearDown() {

        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
    }

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

    @Test
    @Parameters(method = "createDecryptionProof")
    public void testDecryptionProofCreationValidation(ZpSubgroup group,
            ElGamalPublicKey publicKey, Ciphertext ciphertext,
            List<ZpGroupElement> plaintext, ElGamalPrivateKey privateKey,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _proofsServiceForDefaultPolicy.createProofProverAPI(group)
            .createDecryptionProof(publicKey, ciphertext, plaintext,
                privateKey);
    }

    public static Object[] createDecryptionProof() {

        return $(
            $(null, _publicKey, _ciphertext, _plaintext, _privateKey,
                "Zp subgroup is null."),
            $(_zpSubgroup, null, _ciphertext, _plaintext, _privateKey,
                "ElGamal public key is null."),
            $(_zpSubgroup, _publicKey, null, _plaintext, _privateKey,
                "Ciphertext is null."),
            $(_zpSubgroup, _publicKey, _ciphertext, null, _privateKey,
                "Plaintext is null."),
            $(_zpSubgroup, _publicKey, _ciphertext, _emptyPlaintext,
                _privateKey, "Plaintext is empty."),
            $(_zpSubgroup, _publicKey, _ciphertext,
                _plaintextWithNullValue, _privateKey,
                "Plaintext contains one or more null elements."),
            $(_zpSubgroup, _publicKey, _ciphertext, _plaintext, null,
                "ElGamal private key is null."),
            $(_zpSubgroup, _shorterPublicKey, _ciphertext, _plaintext,
                _privateKey,
                "ElGamal public key length must be equal to ElGamal private key length: "
                    + _privateKeyLength + "; Found "
                    + _shorterPublicKeyLength),
            $(_zpSubgroup, _publicKey, _shorterCiphertext, _plaintext,
                _privateKey,
                "Ciphertext length must be equal to ElGamal public key length plus 1: "
                    + _ciphertextLength + "; Found "
                    + _shorterCiphertextLength),
            $(_zpSubgroup, _publicKey, _ciphertext, _shorterPlaintext,
                _privateKey,
                "Plaintext length must be equal to ElGamal public key length: "
                    + _publicKeyLength + "; Found "
                    + _shorterPlaintextLength));
    }

    @Test
    @Parameters(method = "preComputeDecryptionProof")
    public void testDecryptionProofPreComputationValidation(
            ZpSubgroup group, int privateKeyLength, ZpGroupElement gamma,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _proofsServiceForDefaultPolicy.createProofPreComputerAPI(group)
            .preComputeDecryptionProof(privateKeyLength, gamma);
    }

    public static Object[] preComputeDecryptionProof() {

        return $(
            $(null, _privateKeyLength, _gamma, "Zp subgroup is null."),
            $(_zpSubgroup, 0, _gamma,
                "Length of ElGamal private key must be a positive integer; Found 0"),
            $(_zpSubgroup, _privateKeyLength, null,
                "Ciphertext gamma element is null."));
    }

    @Test
    @Parameters(method = "createDecryptionProofUsingPreComputedValues")
    public void testDecryptionProofCreationUsingPreComputedValuesValidation(
            ZpSubgroup group, ElGamalPublicKey publicKey,
            Ciphertext ciphertext, List<ZpGroupElement> plaintext,
            ElGamalPrivateKey privateKey,
            ProofPreComputedValues preComputedValues, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _proofsServiceForDefaultPolicy.createProofProverAPI(group)
            .createDecryptionProof(publicKey, ciphertext, plaintext,
                privateKey, preComputedValues);
    }

    public static Object[] createDecryptionProofUsingPreComputedValues()
            throws GeneralCryptoLibException {

        ProofPreComputedValues preComputedValues =
            _proofsServiceForDefaultPolicy
                .createProofPreComputerAPI(_zpSubgroup)
                .preComputeDecryptionProof(_privateKeyLength, _gamma);

        return $(
            $(null, _publicKey, _ciphertext, _plaintext, _privateKey,
                preComputedValues, "Zp subgroup is null."),
            $(_zpSubgroup, null, _ciphertext, _plaintext, _privateKey,
                preComputedValues, "ElGamal public key is null."),
            $(_zpSubgroup, _publicKey, null, _plaintext, _privateKey,
                preComputedValues, "Ciphertext is null."),
            $(_zpSubgroup, _publicKey, _ciphertext, null, _privateKey,
                preComputedValues, "Plaintext is null."),
            $(_zpSubgroup, _publicKey, _ciphertext, _emptyPlaintext,
                _privateKey, preComputedValues, "Plaintext is empty."),
            $(_zpSubgroup, _publicKey, _ciphertext,
                _plaintextWithNullValue, _privateKey, preComputedValues,
                "Plaintext contains one or more null elements."),
            $(_zpSubgroup, _publicKey, _ciphertext, _plaintext, null,
                preComputedValues, "ElGamal private key is null."),
            $(_zpSubgroup, _shorterPublicKey, _ciphertext, _plaintext,
                _privateKey, preComputedValues,
                "ElGamal public key length must be equal to ElGamal private key length: "
                    + _privateKeyLength + "; Found "
                    + _shorterPublicKeyLength),
            $(_zpSubgroup, _publicKey, _shorterCiphertext, _plaintext,
                _privateKey, preComputedValues,
                "Ciphertext length must be equal to ElGamal public key length plus 1: "
                    + _ciphertextLength + "; Found "
                    + _shorterCiphertextLength),
            $(_zpSubgroup, _publicKey, _ciphertext, _shorterPlaintext,
                _privateKey, preComputedValues,
                "Plaintext length must be equal to ElGamal public key length: "
                    + _publicKeyLength + "; Found "
                    + _shorterPlaintextLength),
            $(_zpSubgroup, _publicKey, _ciphertext, _plaintext,
                _privateKey, null, "Pre-computed values object is null."));
    }

    @Test
    @Parameters(method = "verifyDecryptionProof")
    public void testDecryptionProofVerificationValidation(ZpSubgroup group,
            ElGamalPublicKey publicKey, Ciphertext ciphertext,
            List<ZpGroupElement> plaintext, Proof proof, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _proofsServiceForDefaultPolicy.createProofVerifierAPI(group)
            .verifyDecryptionProof(publicKey, ciphertext, plaintext,
                proof);
    }

    public static Object[] verifyDecryptionProof()
            throws GeneralCryptoLibException {

        Proof proof = _proofsServiceForDefaultPolicy
            .createProofProverAPI(_zpSubgroup).createDecryptionProof(
                _publicKey, _ciphertext, _plaintext, _privateKey);

        return $(
            $(null, _publicKey, _ciphertext, _plaintext, proof,
                "Zp subgroup is null."),
            $(_zpSubgroup, null, _ciphertext, _plaintext, proof,
                "ElGamal public key is null."),
            $(_zpSubgroup, _publicKey, null, _plaintext, proof,
                "Ciphertext is null."),
            $(_zpSubgroup, _publicKey, _ciphertext, null, proof,
                "Plaintext is null."),
            $(_zpSubgroup, _publicKey, _ciphertext, _emptyPlaintext, proof,
                "Plaintext is empty."),
            $(_zpSubgroup, _publicKey, _ciphertext,
                _plaintextWithNullValue, proof,
                "Plaintext contains one or more null elements."),
            $(_zpSubgroup, _publicKey, _ciphertext, _plaintext, null,
                "Decryption proof is null."),
            $(_zpSubgroup, _publicKey, _shorterCiphertext, _plaintext,
                proof,
                "Ciphertext length must be equal to ElGamal public key length plus 1: "
                    + _ciphertextLength + "; Found "
                    + _shorterCiphertextLength),
            $(_zpSubgroup, _publicKey, _ciphertext, _shorterPlaintext,
                proof,
                "Plaintext length must be equal to ElGamal public key length: "
                    + _publicKeyLength + "; Found "
                    + _shorterPlaintextLength));
    }
}
