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

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

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey;
import com.scytl.cryptolib.elgamal.cryptoapi.Ciphertext;
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.primitives.messagedigest.configuration.ConfigMessageDigestAlgorithmAndProvider;
import com.scytl.cryptolib.proofs.maurer.configuration.ConfigProofHashCharset;
import com.scytl.cryptolib.proofs.proof.Proof;
import com.scytl.cryptolib.proofs.utils.HashBuilder;

/**
 * OR proof verifier.
 */
public final class ORProofVerifier {
    private final Ciphertext ciphertext;

    private final ElGamalPublicKey publicKey;

    private final List<ZpGroupElement> elements;

    private final String data;

    private final Proof proof;

    private final ZpSubgroup group;

    private final ConfigMessageDigestAlgorithmAndProvider algorithmAndProvider;

    private final ConfigProofHashCharset charset;

    /**
     * Constructor.
     * 
     * @param ciphertext
     *            the ciphertext
     * @param publicKey
     *            the ElGamal public key used for the encryption
     * @param elements
     *            the group elements
     * @param data
     *            the string data to be included into hash
     * @param proof
     *            the OR proof
     * @param group
     *            the group
     * @param algorithmAndProvider
     *            the hash algorithm and provider
     * @param charset
     *            the charset of the data.
     */
    public ORProofVerifier(Ciphertext ciphertext,
            ElGamalPublicKey publicKey, List<ZpGroupElement> elements,
            String data, Proof proof, ZpSubgroup group,
            ConfigMessageDigestAlgorithmAndProvider algorithmAndProvider,
            ConfigProofHashCharset charset) {
        this.ciphertext = ciphertext;
        this.publicKey = publicKey;
        this.elements = elements;
        this.data = data;
        this.proof = proof;
        this.group = group;
        this.algorithmAndProvider = algorithmAndProvider;
        this.charset = charset;
    }

    /**
     * Verifies an OR proof.
     * 
     * @return true if the proof is correct.
     * @throws GeneralCryptoLibException
     *             failed to verify the proof.
     */
    public boolean verify() throws GeneralCryptoLibException {
        List<ZpGroupElement> values = generateValues();
        Exponent hash = calculateHash(values);
        return hash.equals(proof.getHashValue());
    }

    private Exponent calculateHash(List<ZpGroupElement> values)
            throws GeneralCryptoLibException {
        HashBuilder builder =
            new HashBuilder(algorithmAndProvider, charset);
        byte[] hash = builder.buildHashForProofs(ciphertext.getElements(),
            values, data);
        return new Exponent(group.getQ(), new BigInteger(1, hash));
    }

    private List<ZpGroupElement> generateValues()
            throws GeneralCryptoLibException {
        List<ZpGroupElement> values = new ArrayList<>(elements.size() * 2);
        ZpGroupElement g = group.getGenerator();
        ZpGroupElement h = publicKey.getKeys().get(0);
        ZpGroupElement invertedX = ciphertext.getGamma().invert();
        ZpGroupElement invertedY = ciphertext.getPhis().get(0).invert();
        List<Exponent> exponents = proof.getValuesList();
        for (int i = 0; i < elements.size(); i++) {
            Exponent c = exponents.get(i);
            Exponent z = exponents.get(i + elements.size());
            ZpGroupElement a =
                g.exponentiate(z).multiply(invertedX.exponentiate(c));
            ZpGroupElement e = elements.get(i).multiply(invertedY);
            ZpGroupElement b =
                h.exponentiate(z).multiply(e.exponentiate(c));
            values.add(a);
            values.add(b);
        }
        return values;
    }
}
