package meerkat.crypto.secretsharing.feldman;

import meerkat.crypto.secretsharing.shamir.Polynomial;
import meerkat.crypto.secretsharing.shamir.SecretSharing;
import org.factcenter.qilin.primitives.Group;

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

/**
 * Created by Tzlil on 1/27/2016.
 *
 * an implementation of Feldman's verifiable secret sharing scheme.
 *
 * allows trusted dealer to share a key x among n parties.
 *
 */
public class VerifiableSecretSharing<T> extends SecretSharing {
    /**
     * cyclic group contains g.
     */
    protected final Group<T> group;
    /**
     * a generator of cyclic group of order q.
     *          the generated group is a subgroup of the given group.
     *          it must be chosen such that computing discrete logarithms is hard in this group.
     */
    protected final T g;
    /**
     * commitments to polynomial coefficients.
     *  commitments[k] = g ^ coefficients[k] (group operation)
     */
    protected final ArrayList<T> commitmentsArrayList;

    /**
     * constructor
     * @param q      a large prime.
     * @param t      threshold. Any t+1 share holders can recover the secret,
     *               but any set of at most t share holders cannot
     * @param n      number of share holders
     * @param zi      secret, chosen from Zq
     * @param random use for generate random polynomial
     * @param group
     * @param q a large prime dividing group order.
     * @param g a generator of cyclic group of order q.
     *          the generated group is a subgroup of the given group.
     *          it must be chosen such that computing discrete logarithms is hard in this group.
     */
    public VerifiableSecretSharing(int t, int n, BigInteger zi, Random random, BigInteger q, T g
            , Group<T> group) {
        super(t, n, zi, random,q);
        this.g = g;
        this.group = group;
        assert (this.group.contains(g));
        this.commitmentsArrayList = generateCommitments();
    }

    /**
     * commitments[i] = g ^ polynomial.coefficients[i]
     * @return commitments
     */
    private ArrayList<T> generateCommitments() {

        Polynomial polynomial = getPolynomial();
        BigInteger[] coefficients = polynomial.getCoefficients();
        ArrayList<T> commitments = new ArrayList<T>(t + 1);
        for (int i = 0 ; i <= t;i++){
            commitments.add(i,group.multiply(g,coefficients[i]));
        }
        return commitments;
    }

    /**
     * Compute verification value (g^{share value}) using coefficient commitments sent by dealer and my share id.
     * @param j my share holder id
     * @param commitments commitments to polynomial coefficients of share (received from dealer)
     * @param group
     *
     * @return product of Aik ^ (j ^ k) == g ^ polynomial(i)
     */
    public static <T> T computeVerificationValue(int j, ArrayList<T> commitments, Group<T> group) {
        T v = group.zero();
        BigInteger power = BigInteger.ONE;
        BigInteger J = BigInteger.valueOf(j);
        for (int k = 0 ; k < commitments.size() ; k ++){
            v = group.add(v,group.multiply(commitments.get(k),power));
            power = power.multiply(J);
        }
        return v;
    }

    /**
     * getter
     * @return generator of group
     */
    public T getGenerator() {
        return g;
    }

    /**
     * getter
     * @return group
     */
    public Group<T> getGroup(){
        return group;
    }

    /**
     * getter
     * @return commitmentsArrayList
     */
    public ArrayList<T> getCommitmentsArrayList() {
        return commitmentsArrayList;
    }

}
