/*
 * 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.products.ov.mixnet.proofs.bg.hadamard;

import java.math.BigInteger;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.scytl.cryptolib.api.exceptions.CryptoLibException;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.mathematical.groups.impl.Exponent;
import com.scytl.products.ov.mixnet.commons.beans.proofs.HadamardProductProofAnswer;
import com.scytl.products.ov.mixnet.commons.beans.proofs.HadamardProductProofInitialMessage;
import com.scytl.products.ov.mixnet.commons.proofs.bg.commitments.CommitmentParams;
import com.scytl.products.ov.mixnet.commons.proofs.bg.commitments.PrivateCommitment;
import com.scytl.products.ov.mixnet.commons.proofs.bg.commitments.PublicCommitment;
import com.scytl.products.ov.mixnet.commons.tools.ExponentTools;
import com.scytl.products.ov.mixnet.commons.tools.GroupTools;
import com.scytl.products.ov.mixnet.commons.tools.MultiExponentiation;
import com.scytl.products.ov.mixnet.commons.tools.RandomOracleHash;
import com.scytl.products.ov.mixnet.proofs.bg.zero.ZeroProofVerifier;

/**
 *
 */
public class HadamardProductProofVerifier {

    private static final Logger LOG = LoggerFactory.getLogger("std");

    private final CommitmentParams params;

    private final PublicCommitment[] cA;

    private final PublicCommitment cB;

    private final int n;

    private int m;

    private final RandomOracleHash rO;

    private final BigInteger groupOrder;

    private final MultiExponentiation multiExponentiation;

    public HadamardProductProofVerifier(final int n, final int m, final CommitmentParams params,
            final PublicCommitment[] cA, final PublicCommitment cB, final BigInteger groupOrder,
            final MultiExponentiation multiExponentiation) {
        this.params = params;
        this.n = n;

        if (m != cA.length && m == 1) {
            this.m = 2;
        } else {
        	this.m = m;
        }

        this.cA = cA;
        this.cB = cB;

        this.groupOrder = groupOrder;
        this.rO = new RandomOracleHash(this.groupOrder);

        this.multiExponentiation = multiExponentiation;
    }

    public boolean verify(final HadamardProductProofInitialMessage initial, final HadamardProductProofAnswer answer) {

        try {
            boolean correct = true;

            final PublicCommitment[] cGivenB = initial.getCommitmentPublicB();

            // checking length of cB and that they are all group elements

            if ((cGivenB.length != m) && (cGivenB.length != 1)) {
                correct = false;
                LOG.error("ERROR(Hadamard Argument): commitment to B does not have the expected length");
            }

            for (int i = 0; i < cGivenB.length && correct; i++) {
                if (!GroupTools.isGroupElement(cGivenB[i].getElement())) {
                    LOG.error("ERROR(Zero Argument): cB[" + i + "] is not a group element");
                    correct = false;
                }
            }

            // checking that first and last commitments are the ones that they
            // should be.

            if (!cGivenB[0].isEqual(cA[0])) {
                correct = false;
                LOG.error("ERROR(Hadamard Argument): commitment to B[0] does not correspond to commitment to A[0]");
            }

            if (!cGivenB[m - 1].isEqual(cB)) {

                if (!cGivenB[1].isEqual(cB)) {

                    correct = false;

                    LOG.error("ERROR(Hadamard Argument): commitment to B[m-1] does not correspond to commitment to b");
                }
            }

            final PublicCommitment[] cD = new PublicCommitment[m];

            rO.addDataToRO(cA);
            rO.addDataToRO(cB);
            rO.addDataToRO(initial);
            final Exponent challengeX = rO.getHash();
            rO.addDataToRO("1");
            final Exponent challengeInnerProduct = rO.getHash();

            // preparing commitment cD for the Zero Argument

            Exponent acumulator = challengeX;
            // need to start aux at some point, can't use concept of 0 or identity
            // so I remove the first step in the loop
            cD[0] = cGivenB[0].exponentiate(acumulator);
            PublicCommitment aux = cGivenB[1].exponentiate(acumulator);
            for (int i = 1; i < cD.length - 1; i++) {
                acumulator = acumulator.multiply(challengeX);
                cD[i] = cGivenB[i].exponentiate(acumulator);
                aux = aux.multiply(cGivenB[i + 1].exponentiate(acumulator));

            }
            cD[m - 1] = aux;

            // prepare the other input for the Zero Argument
            final PublicCommitment[] cZeroArgumentA = new PublicCommitment[m];
            System.arraycopy(cA, 1, cZeroArgumentA, 0, m - 1);
            cZeroArgumentA[m - 1] = (new PrivateCommitment(ExponentTools.getMinus1Vector(n, groupOrder),
                new Exponent(groupOrder, BigInteger.ZERO), params, multiExponentiation))
                    .makePublicCommitment();

            final ZeroProofVerifier verifZero = new ZeroProofVerifier(m, n, params, cZeroArgumentA, cD, groupOrder,
                challengeInnerProduct, multiExponentiation);

            if (correct) {
                correct = verifZero.verify(answer.getInitial(), answer.getAnswer());
            }

            if (correct) {
                LOG.info("The Hadamard Argument was verified successfully!");
            }
            return correct;

        } catch (GeneralCryptoLibException ex) {
            throw new CryptoLibException(ex);
        }
    }
}
