package org.spongycastle.pqc.crypto.rainbow;

import java.security.SecureRandom;

import org.spongycastle.crypto.CipherParameters;
import org.spongycastle.crypto.params.ParametersWithRandom;
import org.spongycastle.pqc.crypto.MessageSigner;
import org.spongycastle.pqc.crypto.rainbow.util.ComputeInField;
import org.spongycastle.pqc.crypto.rainbow.util.GF2Field;

/**
 * It implements the sign and verify functions for the Rainbow Signature Scheme.
 * Here the message, which has to be signed, is updated. The use of
 * different hash functions is possible.
 * <p/>
 * Detailed information about the signature and the verify-method is to be found
 * in the paper of Jintai Ding, Dieter Schmidt: Rainbow, a New Multivariable
 * Polynomial Signature Scheme. ACNS 2005: 164-175
 * (http://dx.doi.org/10.1007/11496137_12)
 */
public class RainbowSigner
    implements MessageSigner
{
    // Source of randomness
    private SecureRandom random;

    // The length of a document that can be signed with the privKey
    int signableDocumentLength;

    // Container for the oil and vinegar variables of all the layers
    private short[] x;

    private ComputeInField cf = new ComputeInField();

    RainbowKeyParameters key;

    public void init(boolean forSigning,
                     CipherParameters param)
    {
        if (forSigning)
        {
            if (param instanceof ParametersWithRandom)
            {
                ParametersWithRandom rParam = (ParametersWithRandom)param;

                this.random = rParam.getRandom();
                this.key = (RainbowPrivateKeyParameters)rParam.getParameters();

            }
            else
            {

                this.random = new SecureRandom();
                this.key = (RainbowPrivateKeyParameters)param;
            }
        }
        else
        {
            this.key = (RainbowPublicKeyParameters)param;
        }

        this.signableDocumentLength = this.key.getDocLength();
    }


    /**
     * initial operations before solving the Linear equation system.
     *
     * @param layer the current layer for which a LES is to be solved.
     * @param msg   the message that should be signed.
     * @return Y_ the modified document needed for solving LES, (Y_ =
     *         A1^{-1}*(Y-b1)) linear map L1 = A1 x + b1.
     */
    private short[] initSign(Layer[] layer, short[] msg)
    {

        /* preparation: Modifies the document with the inverse of L1 */
        // tmp = Y - b1:
        short[] tmpVec = new short[msg.length];

        tmpVec = cf.addVect(((RainbowPrivateKeyParameters)this.key).getB1(), msg);

        // Y_ = A1^{-1} * (Y - b1) :
        short[] Y_ = cf.multiplyMatrix(((RainbowPrivateKeyParameters)this.key).getInvA1(), tmpVec);

        /* generates the vinegar vars of the first layer at random */
        for (int i = 0; i < layer[0].getVi(); i++)
        {
            x[i] = (short)random.nextInt();
            x[i] = (short)(x[i] & GF2Field.MASK);
        }

        return Y_;
    }

    /**
     * This function signs the message that has been updated, making use of the
     * private key.
     * <p/>
     * For computing the signature, L1 and L2 are needed, as well as LES should
     * be solved for each layer in order to find the Oil-variables in the layer.
     * <p/>
     * The Vinegar-variables of the first layer are random generated.
     *
     * @param message the message
     * @return the signature of the message.
     */
    public byte[] generateSignature(byte[] message)
    {
        Layer[] layer = ((RainbowPrivateKeyParameters)this.key).getLayers();
        int numberOfLayers = layer.length;

        x = new short[((RainbowPrivateKeyParameters)this.key).getInvA2().length]; // all variables

        short[] Y_; // modified document
        short[] y_i; // part of Y_ each polynomial
        int counter; // index of the current part of the doc

        short[] solVec; // the solution of LES pro layer
        short[] tmpVec;

        // the signature as an array of shorts:
        short[] signature;
        // the signature as a byte-array:
        byte[] S = new byte[layer[numberOfLayers - 1].getViNext()];

        short[] msgHashVals = makeMessageRepresentative(message);

        // shows if an exception is caught
        boolean ok;
        do
        {
            ok = true;
            counter = 0;
            try
            {
                Y_ = initSign(layer, msgHashVals);

                for (int i = 0; i < numberOfLayers; i++)
                {

                    y_i = new short[layer[i].getOi()];
                    solVec = new short[layer[i].getOi()]; // solution of LES

                    /* copy oi elements of Y_ into y_i */
                    for (int k = 0; k < layer[i].getOi(); k++)
                    {
                        y_i[k] = Y_[counter];
                        counter++; // current index of Y_
                    }

                    /*
                          * plug in the vars of the previous layer in order to get
                          * the vars of the current layer
                          */
                    solVec = cf.solveEquation(layer[i].plugInVinegars(x), y_i);

                    if (solVec == null)
                    { // LES is not solveable
                        throw new Exception("LES is not solveable!");
                    }

                    /* copy the new vars into the x-array */
                    for (int j = 0; j < solVec.length; j++)
                    {
                        x[layer[i].getVi() + j] = solVec[j];
                    }
                }

                /* apply the inverse of L2: (signature = A2^{-1}*(b2+x)) */
                tmpVec = cf.addVect(((RainbowPrivateKeyParameters)this.key).getB2(), x);
                signature = cf.multiplyMatrix(((RainbowPrivateKeyParameters)this.key).getInvA2(), tmpVec);

                /* cast signature from short[] to byte[] */
                for (int i = 0; i < S.length; i++)
                {
                    S[i] = ((byte)signature[i]);
                }
            }
            catch (Exception se)
            {
                // if one of the LESs was not solveable - sign again
                ok = false;
            }
        }
        while (!ok);
        /* return the signature in bytes */
        return S;
    }

    /**
     * This function verifies the signature of the message that has been
     * updated, with the aid of the public key.
     *
     * @param message the message
     * @param signature the signature of the message
     * @return true if the signature has been verified, false otherwise.
     */
    public boolean verifySignature(byte[] message, byte[] signature)
    {
        short[] sigInt = new short[signature.length];
        short tmp;

        for (int i = 0; i < signature.length; i++)
        {
            tmp = (short)signature[i];
            tmp &= (short)0xff;
            sigInt[i] = tmp;
        }

        short[] msgHashVal = makeMessageRepresentative(message);

        // verify
        short[] verificationResult = verifySignatureIntern(sigInt);

        // compare
        boolean verified = true;
        if (msgHashVal.length != verificationResult.length)
        {
            return false;
        }
        for (int i = 0; i < msgHashVal.length; i++)
        {
            verified = verified && msgHashVal[i] == verificationResult[i];
        }

        return verified;
    }

    /**
     * Signature verification using public key
     *
     * @param signature vector of dimension n
     * @return document hash of length n - v1
     */
    private short[] verifySignatureIntern(short[] signature)
    {

        short[][] coeff_quadratic = ((RainbowPublicKeyParameters)this.key).getCoeffQuadratic();
        short[][] coeff_singular = ((RainbowPublicKeyParameters)this.key).getCoeffSingular();
        short[] coeff_scalar = ((RainbowPublicKeyParameters)this.key).getCoeffScalar();

        short[] rslt = new short[coeff_quadratic.length];// n - v1
        int n = coeff_singular[0].length;
        int offset = 0; // array position
        short tmp = 0; // for scalar

        for (int p = 0; p < coeff_quadratic.length; p++)
        { // no of polynomials
            offset = 0;
            for (int x = 0; x < n; x++)
            {
                // calculate quadratic terms
                for (int y = x; y < n; y++)
                {
                    tmp = GF2Field.multElem(coeff_quadratic[p][offset],
                        GF2Field.multElem(signature[x], signature[y]));
                    rslt[p] = GF2Field.addElem(rslt[p], tmp);
                    offset++;
                }
                // calculate singular terms
                tmp = GF2Field.multElem(coeff_singular[p][x], signature[x]);
                rslt[p] = GF2Field.addElem(rslt[p], tmp);
            }
            // add scalar
            rslt[p] = GF2Field.addElem(rslt[p], coeff_scalar[p]);
        }

        return rslt;
    }

    /**
     * This function creates the representative of the message which gets signed
     * or verified.
     *
     * @param message the message
     * @return message representative
     */
    private short[] makeMessageRepresentative(byte[] message)
    {
        // the message representative
        short[] output = new short[this.signableDocumentLength];

        int h = 0;
        int i = 0;
        do
        {
            if (i >= message.length)
            {
                break;
            }
            output[i] = (short)message[h];
            output[i] &= (short)0xff;
            h++;
            i++;
        }
        while (i < output.length);

        return output;
    }
}
