/*
 * 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.singlevalprod;

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.SingleValueProductProofAnswer;
import com.scytl.products.ov.mixnet.commons.beans.proofs.SingleValueProductProofInitialMessage;
import com.scytl.products.ov.mixnet.commons.proofs.bg.commitments.CommitmentParams;
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;

/**
 *
 */
public class SingleValueProductProofVerifier {

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

    private final CommitmentParams compars;

    private final PublicCommitment cA;

    private final Exponent b;

    private final int n;

    private final RandomOracleHash rO;

    private final MultiExponentiation multiExponentiation;

    public SingleValueProductProofVerifier(final int n, final CommitmentParams compars, final PublicCommitment cA,
            final Exponent b, final BigInteger groupOrder, final MultiExponentiation multiExponentiation) {
        this.compars = compars;
        this.cA = cA;
        this.b = b;
        this.n = n;
        this.rO = new RandomOracleHash(groupOrder);

        this.multiExponentiation = multiExponentiation;
    }

    public boolean verify(final SingleValueProductProofInitialMessage ini, final SingleValueProductProofAnswer ans) {
        boolean correct = true;

        // checking that elements are group elements
        if (!GroupTools.isGroupElement(ini.getCommitmentPublicD().getElement())) {
            LOG.error("ERROR(Single Value Product Argument): cd is not a group element");
            correct = false;
        }

        if (!GroupTools.isGroupElement(ini.getCommitmentPublicLowDelta().getElement())) {
            LOG.error("ERROR(Single Value Product Argument): cdelta is not a group element");
            correct = false;
        }

        if (!GroupTools.isGroupElement(ini.getCommitmentPublicHighDelta().getElement())) {
            LOG.error("ERROR(Single Value Product Argument): cDelta is not a group element");
            correct = false;
        }

        // checking that elements are exponents

        if (ans.getExponentsTildeA().length != n) {
            LOG.error("ERROR(Single Value Product Argument): a tilde does not have the expected length");
            correct = false;
        } else {
            for (int i = 0; i < n; i++) {
                if (!ExponentTools.isExponent(ans.getExponentsTildeA()[i])) {
                    LOG.error("ERROR(Single Value Product Argument): a[" + i + "] is not an exponent");
                    correct = false;
                }
                if (!hasSameOrder(ans.getExponentsTildeA()[i])) {
                    correct = false;
                    LOG.error("ERROR(Single Value Product Argument): a[" + i + "] is not a group element");
                }
            }
        }

        if (ans.getExponentsTildeB().length != n) {
            LOG.error("ERROR(Single Value Product Argument): b tilde does not have the expected length");
            correct = false;
        } else {
            for (int i = 0; i < n; i++) {
                if (!ExponentTools.isExponent(ans.getExponentsTildeB()[i])) {
                    LOG.error("ERROR(Single Value Product Argument): b[" + i + "] is not an exponent");
                    correct = false;
                }
                if (!hasSameOrder(ans.getExponentsTildeB()[i])) {
                    correct = false;
                    LOG.error("ERROR(Single Value Product Argument): b[" + i + "] is not a group element");
                }
            }
        }

        if (!ExponentTools.isExponent(ans.getExponentTildeR())) {
            LOG.error("ERROR(Single Value Product Argument): r tilde is not an exponent");
        }
        if (!hasSameOrder(ans.getExponentTildeR())) {
            correct = false;
            LOG.error("ERROR(Single Value Product Argument): r tilde is not a group element");
        }

        if (!ExponentTools.isExponent(ans.getExponentTildeS())) {
            LOG.error("ERROR(Single Value Product Argument): s tilde is not an exponent");
        }
        if (!hasSameOrder(ans.getExponentTildeS())) {
            correct = false;
            LOG.error("ERROR(Single Value Product Argument): s tilde is not a group element");
        }

        rO.addDataToRO(cA);
        rO.addDataToRO(b);
        rO.addDataToRO(ini);
        final Exponent challengeX = rO.getHash();

        // checking that commitments have correct openings

        final PublicCommitment comCATilde = cA.exponentiate(challengeX).multiply(ini.getCommitmentPublicD());

        final PublicCommitment comCDeltadelta =
            ini.getCommitmentPublicHighDelta().exponentiate(challengeX).multiply(ini.getCommitmentPublicLowDelta());

        try {
            final Exponent[] openingcdeltaDelta = new Exponent[n - 1];
            for (int i = 0; i < openingcdeltaDelta.length; i++) {
                openingcdeltaDelta[i] = challengeX.multiply(ans.getExponentsTildeB()[i + 1])
                    .add(ans.getExponentsTildeB()[i].multiply(ans.getExponentsTildeA()[i + 1]).negate());
            }

            if (!comCDeltadelta.verifyOpening(openingcdeltaDelta, ans.getExponentTildeS(), compars,
                multiExponentiation)) {
                LOG.error("ERROR(Single Value Product Argument): the commitment to Delta or delta is incorrect");
                correct = false;
            }

            if (!comCATilde.verifyOpening(ans.getExponentsTildeA(), ans.getExponentTildeR(), compars,
                multiExponentiation)) {
                LOG.error("ERROR(Single Value Product Argument): the commitment to a or d is incorrect");
                correct = false;
            }

            if (!ans.getExponentsTildeB()[0].equals(ans.getExponentsTildeA()[0])) {
                correct = false;
                LOG.error("ERROR(Single Value Product Argument): tilde a and b not the same");
            }
            if (!ans.getExponentsTildeB()[n - 1].equals(challengeX.multiply(b))) {
                correct = false;
                LOG.error("ERROR(Single Value Product Argument): tilde b and bchallenge are not the same");
            }

            if (correct) {
                LOG.info("The Single Value Product Argument was verified successfully!");
            }

            return correct;

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

    }

    private boolean hasSameOrder(Exponent exponent) {
        return compars.getGroup().getQ().equals(exponent.getQ());
    }
}
