/*
 * 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 java.security.Security;
import java.util.List;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

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;

/**
 * Tests of the Proofs service API for plaintext equality zero knowledge proofs
 * of knowledge.
 */
public class PlaintextEqualityProofTest {

    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 Proof _proof;

    private static ProofPreComputedValues _preComputedValues;

    private static ZpSubgroup _differentZpSubgroup;

    private static ElGamalPublicKey _differentPrimaryPublicKey;

    private static ElGamalPublicKey _differentSecondaryPublicKey;

    private static Ciphertext _differentPrimaryCiphertext;

    private static Witness _differentPrimaryWitness;

    private static Ciphertext _differentSecondaryCiphertext;

    private static Witness _differentSecondaryWitness;

    @BeforeClass
    public static void setUp() throws Exception {

        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;

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

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

        _differentZpSubgroup =
            MathematicalTestDataGenerator.getQrSubgroup();

        do {
            _differentPrimaryPublicKey = ElGamalTestDataGenerator
                .getKeyPair(_zpSubgroup, NUM_PLAINTEXT_ELEMENTS)
                .getPublicKeys();
        } while (_differentPrimaryPublicKey.equals(_primaryPublicKey));
        do {
            _differentSecondaryPublicKey = ElGamalTestDataGenerator
                .getKeyPair(_zpSubgroup, NUM_PLAINTEXT_ELEMENTS)
                .getPublicKeys();
        } while (_differentSecondaryPublicKey.equals(_secondaryPublicKey));

        List<ZpGroupElement> differentPlaintext;
        do {
            differentPlaintext = MathematicalTestDataGenerator
                .getZpGroupElements(_zpSubgroup, NUM_PLAINTEXT_ELEMENTS);
        } while (differentPlaintext.equals(_plaintext));

        do {
            encrypterValues =
                (ElGamalEncrypterValues) ElGamalTestDataGenerator
                    .encryptGroupElements(_primaryPublicKey,
                        differentPlaintext);
            _differentPrimaryCiphertext = encrypterValues;
            _differentPrimaryWitness = encrypterValues;
        } while (_differentPrimaryWitness.getExponent()
            .equals(_primaryWitness.getExponent()));
        do {
            encrypterValues =
                (ElGamalEncrypterValues) ElGamalTestDataGenerator
                    .encryptGroupElements(_secondaryPublicKey,
                        differentPlaintext);
            _differentSecondaryCiphertext = encrypterValues;
            _differentSecondaryWitness = encrypterValues;
        } while (_differentSecondaryWitness.getExponent()
            .equals(_secondaryWitness.getExponent()));
    }

    @AfterClass
    public static void tearDown() {

        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
    }

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

    @Test
    @Deprecated
    public final void whenGenerateAndVerifyProofUsingDeprecatedMethodsThenOk()
            throws GeneralCryptoLibException {

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

        Assert.assertTrue(_proofsServiceForDefaultPolicy
            .createProofVerifierAPI(_zpSubgroup)
            .verifyPlaintextEqualityProof(_primaryCiphertext.getElements(),
                _primaryPublicKey, _secondaryCiphertext.getElements(),
                _secondaryPublicKey, proof));
    }

    @Test
    public final void whenGenerateAndVerifyProofThenOk()
            throws GeneralCryptoLibException {

        Assert.assertTrue(_proofsServiceForDefaultPolicy
            .createProofVerifierAPI(_zpSubgroup)
            .verifyPlaintextEqualityProof(_primaryCiphertext,
                _primaryPublicKey, _secondaryCiphertext,
                _secondaryPublicKey, _proof));
    }

    @Test
    public final void whenGenerateProofUsingPreComputedValuesAndVerifyThenOk()
            throws GeneralCryptoLibException {

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

        Assert.assertTrue(_proofsServiceForDefaultPolicy
            .createProofVerifierAPI(_zpSubgroup)
            .verifyPlaintextEqualityProof(_primaryCiphertext,
                _primaryPublicKey, _secondaryCiphertext,
                _secondaryPublicKey, proof));
    }

    @Test
    public final void whenSerializeAndDeserializeProofThenOk()
            throws GeneralCryptoLibException {

        Proof deserializedProof = Proof.fromJson(_proof.toJson());

        Assert.assertTrue(_proofsServiceForDefaultPolicy
            .createProofVerifierAPI(_zpSubgroup)
            .verifyPlaintextEqualityProof(_primaryCiphertext,
                _primaryPublicKey, _secondaryCiphertext,
                _secondaryPublicKey, deserializedProof));
    }

    @Test
    public final void whenGenerateProofUsingInvalidPreComputedValuesThenVerificationIsFalse()
            throws GeneralCryptoLibException {

        ProofPreComputedValues differentPreComputedValues;
        do {
            differentPreComputedValues = _proofsServiceForDefaultPolicy
                .createProofPreComputerAPI(_zpSubgroup)
                .preComputePlaintextEqualityProof(
                    _differentPrimaryPublicKey,
                    _differentSecondaryPublicKey);
        } while (differentPreComputedValues.getExponents()
            .equals(_preComputedValues.getExponents()));

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

        Assert.assertFalse(_proofsServiceForDefaultPolicy
            .createProofVerifierAPI(_zpSubgroup)
            .verifyPlaintextEqualityProof(_primaryCiphertext,
                _primaryPublicKey, _secondaryCiphertext,
                _secondaryPublicKey, proof));
    }

    @Test
    public final void whenGenerateProofUsingInvalidPrimaryCiphertextThenVerificationIsFalse()
            throws GeneralCryptoLibException {

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

        Assert.assertFalse(_proofsServiceForDefaultPolicy
            .createProofVerifierAPI(_zpSubgroup)
            .verifyPlaintextEqualityProof(_differentPrimaryCiphertext,
                _primaryPublicKey, _secondaryCiphertext,
                _secondaryPublicKey, proof));
    }

    @Test
    public final void whenGenerateProofUsingInvalidPrimaryPublicKeyThenVerificationIsFalse()
            throws GeneralCryptoLibException {

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

        Assert.assertFalse(_proofsServiceForDefaultPolicy
            .createProofVerifierAPI(_zpSubgroup)
            .verifyPlaintextEqualityProof(_primaryCiphertext,
                _differentPrimaryPublicKey, _secondaryCiphertext,
                _secondaryPublicKey, proof));
    }

    @Test
    public final void whenGenerateProofUsingInvalidPrimaryWitnessThenVerificationIsFalse()
            throws GeneralCryptoLibException {

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

        Assert.assertFalse(_proofsServiceForDefaultPolicy
            .createProofVerifierAPI(_zpSubgroup)
            .verifyPlaintextEqualityProof(_primaryCiphertext,
                _primaryPublicKey, _secondaryCiphertext,
                _secondaryPublicKey, proof));
    }

    @Test
    public final void whenGenerateProofUsingInvalidSecondaryCiphertextThenVerificationIsFalse()
            throws GeneralCryptoLibException {

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

        Assert.assertFalse(_proofsServiceForDefaultPolicy
            .createProofVerifierAPI(_zpSubgroup)
            .verifyPlaintextEqualityProof(_primaryCiphertext,
                _primaryPublicKey, _differentSecondaryCiphertext,
                _secondaryPublicKey, proof));
    }

    @Test
    public final void whenGenerateProofUsingInvalidSecondaryPublicKeyThenVerificationIsFalse()
            throws GeneralCryptoLibException {

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

        Assert.assertFalse(_proofsServiceForDefaultPolicy
            .createProofVerifierAPI(_zpSubgroup)
            .verifyPlaintextEqualityProof(_primaryCiphertext,
                _primaryPublicKey, _secondaryCiphertext,
                _differentSecondaryPublicKey, proof));
    }

    @Test
    public final void whenGenerateProofUsingInvalidSecondaryWitnessThenVerificationIsFalse()
            throws GeneralCryptoLibException {

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

        Assert.assertFalse(_proofsServiceForDefaultPolicy
            .createProofVerifierAPI(_zpSubgroup)
            .verifyPlaintextEqualityProof(_primaryCiphertext,
                _primaryPublicKey, _secondaryCiphertext,
                _secondaryPublicKey, proof));
    }

    @Test
    public final void whenVerifyProofUsingInvalidPrimaryCiphertextThenVerificationIsFalse()
            throws GeneralCryptoLibException {

        Assert.assertFalse(_proofsServiceForDefaultPolicy
            .createProofVerifierAPI(_zpSubgroup)
            .verifyPlaintextEqualityProof(_differentPrimaryCiphertext,
                _primaryPublicKey, _secondaryCiphertext,
                _secondaryPublicKey, _proof));
    }

    @Test
    public final void whenVerifyProofUsingInvalidPrimaryPublicKeyThenVerificationIsFalse()
            throws GeneralCryptoLibException {

        Assert.assertFalse(_proofsServiceForDefaultPolicy
            .createProofVerifierAPI(_zpSubgroup)
            .verifyPlaintextEqualityProof(_primaryCiphertext,
                _differentPrimaryPublicKey, _secondaryCiphertext,
                _secondaryPublicKey, _proof));
    }

    @Test
    public final void whenVerifyProofUsingInvalidSecondaryCiphertextThenVerificationIsFalse()
            throws GeneralCryptoLibException {

        Assert.assertFalse(_proofsServiceForDefaultPolicy
            .createProofVerifierAPI(_zpSubgroup)
            .verifyPlaintextEqualityProof(_primaryCiphertext,
                _primaryPublicKey, _differentSecondaryCiphertext,
                _secondaryPublicKey, _proof));
    }

    @Test
    public final void whenVerifyProofUsingInvalidSecondaryPublicKeyThenVerificationIsFalse()
            throws GeneralCryptoLibException {

        Assert.assertFalse(_proofsServiceForDefaultPolicy
            .createProofVerifierAPI(_zpSubgroup)
            .verifyPlaintextEqualityProof(_primaryCiphertext,
                _primaryPublicKey, _secondaryCiphertext,
                _differentSecondaryPublicKey, _proof));
    }

    @Test
    public final void whenGenerateProofUsingInvalidMathematicalGroupThenExceptionThrown()
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(
            "The exponent should be of the same Zp subgroup order as this Zp group element");

        _proofsServiceForDefaultPolicy
            .createProofProverAPI(_differentZpSubgroup)
            .createPlaintextEqualityProof(_primaryCiphertext,
                _primaryPublicKey, _primaryWitness, _secondaryCiphertext,
                _secondaryPublicKey, _secondaryWitness);
    }

    @Test
    public final void whenVerifyProofUsingInvalidMathematicalGroupThenExceptionThrown()
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(
            "All elements of list of public values must be elements of mathematical group associated with proof.");

        _proofsServiceForDefaultPolicy
            .createProofVerifierAPI(_differentZpSubgroup)
            .verifyPlaintextEqualityProof(_primaryCiphertext,
                _primaryPublicKey, _secondaryCiphertext,
                _secondaryPublicKey, _proof);
    }
}
