/*
 * 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 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.api.proofs.ProofsServiceAPI;
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.primitives.primes.utils.PrimitivesTestDataGenerator;
import com.scytl.cryptolib.primitives.securerandom.constants.SecureRandomConstants;
import com.scytl.cryptolib.proofs.bean.ProofPreComputedValues;
import com.scytl.cryptolib.proofs.proof.Proof;
import com.scytl.cryptolib.test.tools.utils.CommonTestDataGenerator;

/**
 * Tests of the Proofs service API for Schnorr zero knowledge proofs of
 * knowledge.
 */
public class SchnorrProofTest {

    private static final String PUBLIC_VALUES_LIST_WITH_INVALID_ELEMENT_ERROR_MESSAGE =
        "All elements of list of public values must be elements of mathematical group associated with proof.";

    private static ProofsServiceAPI _proofsServiceForDefaultPolicy;

    private static ZpSubgroup _zpSubgroup;

    private static String _voterId;

    private static String _electionEventId;

    private static Witness _witness;

    private static ZpGroupElement _exponentiatedGenerator;

    private static Proof _proof;

    private static ProofPreComputedValues _preComputedValues;

    private static ZpSubgroup _differentZpSubgroup;

    private static ZpGroupElement _invalidExponentiatedGenerator;

    private static ZpGroupElement _exponentiatedSubgroupOrder;

    @BeforeClass
    public static void setUp() throws Exception {

        Security.addProvider(new BouncyCastleProvider());

        _proofsServiceForDefaultPolicy = new ProofsService();

        _zpSubgroup = MathematicalTestDataGenerator.getZpSubgroup();

        int numChars = CommonTestDataGenerator.getInt(1,
            SecureRandomConstants.MAXIMUM_GENERATED_STRING_LENGTH);
        _voterId = PrimitivesTestDataGenerator.getString64(numChars);
        _electionEventId =
            PrimitivesTestDataGenerator.getString64(numChars);

        _witness = ElGamalTestDataGenerator.getWitness(_zpSubgroup);
        ZpGroupElement generatorElement =
            new ZpGroupElement(_zpSubgroup.getG(), _zpSubgroup);
        _exponentiatedGenerator =
            generatorElement.exponentiate(_witness.getExponent());

        _proof = _proofsServiceForDefaultPolicy
            .createProofProverAPI(_zpSubgroup).createSchnorrProof(_voterId,
                _electionEventId, _exponentiatedGenerator, _witness);

        _preComputedValues = _proofsServiceForDefaultPolicy
            .createProofPreComputerAPI(_zpSubgroup)
            .preComputeSchnorrProof();

        _differentZpSubgroup =
            MathematicalTestDataGenerator.getQrSubgroup();

        ZpGroupElement invalidGenerator =
            new ZpGroupElement(_zpSubgroup.getG(), _differentZpSubgroup);
        _invalidExponentiatedGenerator =
            invalidGenerator.exponentiate(ElGamalTestDataGenerator
                .getWitness(_differentZpSubgroup).getExponent());

        ZpGroupElement subgroupOrder =
            new ZpGroupElement(_zpSubgroup.getQ(), _zpSubgroup);
        _exponentiatedSubgroupOrder =
            subgroupOrder.exponentiate(_witness.getExponent());
    }

    @AfterClass
    public static void tearDown() {

        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
    }

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

    @Test
    public final void whenGenerateAndVerifyProofThenOk()
            throws GeneralCryptoLibException {

        Assert.assertTrue(_proofsServiceForDefaultPolicy
            .createProofVerifierAPI(_zpSubgroup)
            .verifySchnorrProof(_exponentiatedGenerator, _voterId,
                _electionEventId, _proof));
    }

    @Test
    public final void whenGenerateProofUsingPreComputedValuesAndVerifyThenOk()
            throws GeneralCryptoLibException {

        Proof proof = _proofsServiceForDefaultPolicy
            .createProofProverAPI(_zpSubgroup).createSchnorrProof(_voterId,
                _electionEventId, _exponentiatedGenerator, _witness,
                _preComputedValues);

        Assert.assertTrue(_proofsServiceForDefaultPolicy
            .createProofVerifierAPI(_zpSubgroup)
            .verifySchnorrProof(_exponentiatedGenerator, _voterId,
                _electionEventId, proof));
    }

    @Test
    public final void whenSerializeAndDeserializeProofThenOk()
            throws GeneralCryptoLibException {

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

        Assert.assertTrue(_proofsServiceForDefaultPolicy
            .createProofVerifierAPI(_zpSubgroup)
            .verifySchnorrProof(_exponentiatedGenerator, _voterId,
                _electionEventId, deserializedProof));
    }

    @Test
    public final void whenGenerateProofUsingInvalidWitnessThenVerificationIsFalse()
            throws GeneralCryptoLibException {

        Witness differentWitness;
        do {
            differentWitness =
                ElGamalTestDataGenerator.getWitness(_zpSubgroup);
        } while (differentWitness.getExponent()
            .equals(_witness.getExponent()));

        Proof proof = _proofsServiceForDefaultPolicy
            .createProofProverAPI(_zpSubgroup).createSchnorrProof(_voterId,
                _electionEventId, _exponentiatedGenerator,
                differentWitness);

        Assert.assertFalse(_proofsServiceForDefaultPolicy
            .createProofVerifierAPI(_zpSubgroup)
            .verifySchnorrProof(_exponentiatedGenerator, _voterId,
                _electionEventId, proof));
    }

    @Test
    public final void whenVerifyProofUsingInvalidBusinessDataThenVerificationIsFalse()
            throws GeneralCryptoLibException {

        Assert.assertFalse(_proofsServiceForDefaultPolicy
            .createProofVerifierAPI(_zpSubgroup)
            .verifySchnorrProof(_exponentiatedGenerator, _voterId + "0",
                _electionEventId, _proof));

        Assert.assertFalse(_proofsServiceForDefaultPolicy
            .createProofVerifierAPI(_zpSubgroup)
            .verifySchnorrProof(_exponentiatedGenerator, _voterId,
                _electionEventId + "0", _proof));
    }

    @Test
    public final void whenVerifyProofGeneratedUsingExponentiatedNonGeneratorThenExceptionThrown()
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(
            PUBLIC_VALUES_LIST_WITH_INVALID_ELEMENT_ERROR_MESSAGE);

        Proof proof = _proofsServiceForDefaultPolicy
            .createProofProverAPI(_zpSubgroup).createSchnorrProof(_voterId,
                _electionEventId, _exponentiatedSubgroupOrder, _witness);

        _proofsServiceForDefaultPolicy.createProofVerifierAPI(_zpSubgroup)
            .verifySchnorrProof(_exponentiatedSubgroupOrder, _voterId,
                _electionEventId, proof);
    }

    @Test
    public final void whenVerifyProofGeneratedUsingInvalidExponentiatedGeneratorThenExceptionThrown()
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(
            PUBLIC_VALUES_LIST_WITH_INVALID_ELEMENT_ERROR_MESSAGE);

        Proof proof = _proofsServiceForDefaultPolicy
            .createProofProverAPI(_zpSubgroup).createSchnorrProof(_voterId,
                _electionEventId, _invalidExponentiatedGenerator,
                _witness);

        _proofsServiceForDefaultPolicy.createProofVerifierAPI(_zpSubgroup)
            .verifySchnorrProof(_invalidExponentiatedGenerator, _voterId,
                _electionEventId, proof);
    }

    @Test
    public final void whenVerifyProofUsingInvalidMathematicalGroupThenExceptionIsThrown()
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(
            PUBLIC_VALUES_LIST_WITH_INVALID_ELEMENT_ERROR_MESSAGE);

        _proofsServiceForDefaultPolicy
            .createProofVerifierAPI(_differentZpSubgroup)
            .verifySchnorrProof(_exponentiatedGenerator, _voterId,
                _electionEventId, _proof);
    }

    @Test
    public final void whenVerifyProofUsingExponentiatedNonGeneratorThenExceptionThrown()
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(
            PUBLIC_VALUES_LIST_WITH_INVALID_ELEMENT_ERROR_MESSAGE);

        _proofsServiceForDefaultPolicy.createProofVerifierAPI(_zpSubgroup)
            .verifySchnorrProof(_exponentiatedSubgroupOrder, _voterId,
                _electionEventId, _proof);
    }

    @Test
    public final void whenVerifyProofUsingInvalidExponentiatedGeneratorThenExceptionThrown()
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(
            PUBLIC_VALUES_LIST_WITH_INVALID_ELEMENT_ERROR_MESSAGE);

        _proofsServiceForDefaultPolicy.createProofVerifierAPI(_zpSubgroup)
            .verifySchnorrProof(_invalidExponentiatedGenerator, _voterId,
                _electionEventId, _proof);
    }
}
