/*
 * 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.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.bean.SimplePlaintextCiphertextPair;
import com.scytl.cryptolib.proofs.bean.SimplePlaintextPublicKeyPair;
import com.scytl.cryptolib.proofs.proof.Proof;
import com.scytl.cryptolib.proofs.utils.ProofsTestDataGenerator;

import junitparams.JUnitParamsRunner;
import junitparams.Parameters;

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

    private static final int NUM_PLAINTEXT_ELEMENTS = 6;

    private static ProofsService _proofsServiceForDefaultPolicy;

    private static ZpSubgroup _zpSubgroup;

    private static ElGamalPublicKey _parentPublicKey;

    private static ElGamalPublicKey _primaryPublicKey;

    private static ElGamalPublicKey _secondaryPublicKey;

    private static Ciphertext _primaryCiphertext;

    private static Ciphertext _secondaryCiphertext;

    private static Witness _witness;

    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 "
                + SimplePlaintextEqualityProofValidationTest.class
                    .getSimpleName());
        }
    }

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

        _proofsServiceForDefaultPolicy = new ProofsService();

        _zpSubgroup = MathematicalTestDataGenerator.getZpSubgroup();

        _parentPublicKey = ElGamalTestDataGenerator
            .getKeyPair(_zpSubgroup, NUM_PLAINTEXT_ELEMENTS)
            .getPublicKeys();

        SimplePlaintextPublicKeyPair publicKeyPair =
            ProofsTestDataGenerator.getSimplePlaintextPublicKeyPair(
                _zpSubgroup, _parentPublicKey);
        _primaryPublicKey = publicKeyPair.getPrimaryPublicKey();
        _secondaryPublicKey = publicKeyPair.getSecondaryPublicKey();

        List<ZpGroupElement> simplePlaintext = ProofsTestDataGenerator
            .getSimplePlaintext(_zpSubgroup, NUM_PLAINTEXT_ELEMENTS);

        SimplePlaintextCiphertextPair ciphertextPair =
            ProofsTestDataGenerator
                .encryptSimplePlaintext(_parentPublicKey, simplePlaintext);
        _primaryCiphertext = ciphertextPair.getPrimaryCiphertext();
        _secondaryCiphertext = ciphertextPair.getSecondaryCiphertext();
        _witness = ciphertextPair.getWitness();

        ElGamalPublicKey shorterParentPublicKey = ElGamalTestDataGenerator
            .getKeyPair(_zpSubgroup, (NUM_PLAINTEXT_ELEMENTS - 2))
            .getPublicKeys();
        SimplePlaintextPublicKeyPair shorterPublicKeyPair =
            ProofsTestDataGenerator.getSimplePlaintextPublicKeyPair(
                _zpSubgroup, shorterParentPublicKey);
        _shorterPrimaryPublicKey =
            shorterPublicKeyPair.getPrimaryPublicKey();
        _shorterSecondaryPublicKey =
            shorterPublicKeyPair.getSecondaryPublicKey();
        List<ZpGroupElement> shorterSimplePlaintext =
            ProofsTestDataGenerator.getSimplePlaintext(_zpSubgroup,
                (NUM_PLAINTEXT_ELEMENTS - 2));
        SimplePlaintextCiphertextPair shorterCiphertextPair =
            ProofsTestDataGenerator.encryptSimplePlaintext(
                shorterParentPublicKey, shorterSimplePlaintext);
        _shorterPrimaryCiphertext =
            shorterCiphertextPair.getPrimaryCiphertext();

        _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 = "createSimplePlaintextEqualityProof")
    public void testSimplePlaintextEqualityProofCreationValidation(
            ZpSubgroup group, Ciphertext primaryCiphertext,
            ElGamalPublicKey primaryPublicKey, Witness witness,
            Ciphertext secondaryCiphertext,
            ElGamalPublicKey secondaryPublicKey, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _proofsServiceForDefaultPolicy.createProofProverAPI(group)
            .createSimplePlaintextEqualityProof(primaryCiphertext,
                primaryPublicKey, witness, secondaryCiphertext,
                secondaryPublicKey);
    }

    public static Object[] createSimplePlaintextEqualityProof() {

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

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

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

    public static Object[] preComputeSimplePlaintextEqualityProofProof() {

        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 = "createSimplePlaintextEqualityProofUsingPreComputedValues")
    public void testSimplePlaintextEqualityProofCreationUsingPreComputedValuesValidation(
            ZpSubgroup group, Ciphertext primaryCiphertext,
            ElGamalPublicKey primaryPublicKey, Witness witness,
            Ciphertext secondaryCiphertext,
            ElGamalPublicKey secondaryPublicKey,
            ProofPreComputedValues preComputedValues, String errorMsg)
            throws GeneralCryptoLibException {

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

    public static Object[] createSimplePlaintextEqualityProofUsingPreComputedValues()
            throws GeneralCryptoLibException {

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

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

    @Test
    @Parameters(method = "verifySimplePlaintextEqualityProof")
    public void testSimplePlaintextEqualityProofVerificationValidation(
            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)
            .verifySimplePlaintextEqualityProof(primaryCiphertext,
                primaryPublicKey, secondaryCiphertext, secondaryPublicKey,
                proof);
    }

    public static Object[] verifySimplePlaintextEqualityProof()
            throws GeneralCryptoLibException {

        Proof proof = _proofsServiceForDefaultPolicy
            .createProofProverAPI(_zpSubgroup)
            .createSimplePlaintextEqualityProof(_primaryCiphertext,
                _primaryPublicKey, _witness, _secondaryCiphertext,
                _secondaryPublicKey);

        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,
                "Simple 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));
    }
}
