/*
 * 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 java.util.Collections.singletonList;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

import org.junit.Before;
import org.junit.Test;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.api.proofs.ProofsServiceAPI;
import com.scytl.cryptolib.elgamal.bean.CiphertextImpl;
import com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey;
import com.scytl.cryptolib.elgamal.bean.WitnessImpl;
import com.scytl.cryptolib.elgamal.cryptoapi.Ciphertext;
import com.scytl.cryptolib.elgamal.cryptoapi.Witness;
import com.scytl.cryptolib.mathematical.groups.impl.Exponent;
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.cryptoapi.ProofPreComputerAPI;
import com.scytl.cryptolib.proofs.cryptoapi.ProofProverAPI;
import com.scytl.cryptolib.proofs.cryptoapi.ProofVerifierAPI;
import com.scytl.cryptolib.proofs.proof.Proof;

/**
 * Tests of OR proofs.
 */
public class ORProofTest {
    private ZpSubgroup group;

    private ProofPreComputerAPI preComputer;

    private ProofProverAPI prover;

    private ProofVerifierAPI verifier;

    private Ciphertext ciphertext;

    private ElGamalPublicKey publicKey;

    private Witness witness;

    private int index;

    private List<ZpGroupElement> elements;

    private String data;

    @Before
    public void setUp() throws GeneralCryptoLibException {
        group = MathematicalTestDataGenerator.getZpSubgroup();

        ProofsServiceAPI service = new ProofsService();
        preComputer = service.createProofPreComputerAPI(group);
        prover = service.createProofProverAPI(group);
        verifier = service.createProofVerifierAPI(group);

        Exponent privateKey =
            new Exponent(group.getQ(), BigInteger.valueOf(2));
        ZpGroupElement publicKeyComponent =
            group.getGenerator().exponentiate(privateKey);
        publicKey =
            new ElGamalPublicKey(singletonList(publicKeyComponent), group);

        ZpGroupElement element = group.getGenerator();
        elements = new ArrayList<>();
        elements.add(element);
        element = element.multiply(group.getGenerator());
        elements.add(element);
        element = element.multiply(group.getGenerator());
        elements.add(element);
        element = element.multiply(group.getGenerator());
        elements.add(element);
        element = element.multiply(group.getGenerator());
        elements.add(element);

        index = 3;

        Exponent exponent =
            new Exponent(group.getQ(), BigInteger.valueOf(3));

        ciphertext = new CiphertextImpl(
            group.getGenerator().exponentiate(exponent), publicKeyComponent
                .exponentiate(exponent).multiply(elements.get(index)));

        witness = new WitnessImpl(exponent);

        data = "data";
    }

    @Test
    public void testDifferentCiphertext()
            throws GeneralCryptoLibException {
        Proof proof = prover.createORProof(ciphertext, publicKey, witness,
            index, elements, data);

        ZpGroupElement key = publicKey.getKeys().get(0);

        Exponent exponent =
            new Exponent(group.getQ(), BigInteger.valueOf(3));

        ciphertext = new CiphertextImpl(
            group.getGenerator().exponentiate(exponent),
            key.exponentiate(exponent).multiply(elements.get(index + 1)));

        assertFalse(verifier.verifyORProof(ciphertext, publicKey, elements,
            data, proof));
    }

    @Test
    public void testDifferentData() throws GeneralCryptoLibException {
        Proof proof = prover.createORProof(ciphertext, publicKey, witness,
            index, elements, data);
        assertFalse(verifier.verifyORProof(ciphertext, publicKey, elements,
            data + "something else", proof));
    }

    @Test
    public void testDifferentElements() throws GeneralCryptoLibException {
        Proof proof = prover.createORProof(ciphertext, publicKey, witness,
            index, elements, data);

        elements.set(0, elements.get(0).invert());

        assertFalse(verifier.verifyORProof(ciphertext, publicKey, elements,
            data, proof));
    }

    @Test
    public void testDifferentKey() throws GeneralCryptoLibException {
        Proof proof = prover.createORProof(ciphertext, publicKey, witness,
            index, elements, data);

        Exponent privateKey =
            new Exponent(group.getQ(), BigInteger.valueOf(3));
        ZpGroupElement publicKeyComponent =
            group.getGenerator().exponentiate(privateKey);
        publicKey =
            new ElGamalPublicKey(singletonList(publicKeyComponent), group);

        assertFalse(verifier.verifyORProof(ciphertext, publicKey, elements,
            data, proof));
    }

    @Test
    public void testHappyPath() throws GeneralCryptoLibException {
        Proof proof = prover.createORProof(ciphertext, publicKey, witness,
            index, elements, data);
        assertTrue(verifier.verifyORProof(ciphertext, publicKey, elements,
            data, proof));
    }

    @Test
    public void testHappyPathPreComputation()
            throws GeneralCryptoLibException {
        ProofPreComputedValues preComputedValues =
            preComputer.preComputeORProof(publicKey, elements.size());
        Proof proof = prover.createORProof(ciphertext, publicKey, witness,
            index, elements, data, preComputedValues);
        assertTrue(verifier.verifyORProof(ciphertext, publicKey, elements,
            data, proof));
    }
}
