package org.bouncycastle.crypto.signers;

import org.bouncycastle.math.ec.ECConstants;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.crypto.params.ECKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.DSA;

import java.security.SecureRandom;
import java.math.BigInteger;

/**
 * GOST R 34.10-2001 Signature Algorithm
 */
public class ECGOST3410Signer
    implements DSA
{
    ECKeyParameters key;

    SecureRandom    random;

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

                this.random = rParam.getRandom();
                this.key = (ECPrivateKeyParameters)rParam.getParameters();
            }
            else
            {
                this.random = new SecureRandom();
                this.key = (ECPrivateKeyParameters)param;
            }
        }
        else
        {
            this.key = (ECPublicKeyParameters)param;
        }
    }

    /**
     * generate a signature for the given message using the key we were
     * initialised with. For conventional GOST3410 the message should be a GOST3411
     * hash of the message of interest.
     *
     * @param message the message that will be verified later.
     */
    public BigInteger[] generateSignature(
        byte[] message)
    {
        byte[] mRev = new byte[message.length]; // conversion is little-endian
        for (int i = 0; i != mRev.length; i++)
        {
            mRev[i] = message[mRev.length - 1 - i];
        }
        
        BigInteger e = new BigInteger(1, mRev);
        BigInteger n = key.getParameters().getN();

        BigInteger r = null;
        BigInteger s = null;

        do // generate s
        {
            BigInteger k = null;

            do // generate r
            {
                do
                {
                    k = new BigInteger(n.bitLength(), random);
                }
                while ( k.equals(ECConstants.ZERO) );

                ECPoint p = key.getParameters().getG().multiply(k);

                BigInteger x = p.getX().toBigInteger();

                r = x.mod(n);
            }
            while ( r.equals(ECConstants.ZERO) );

            BigInteger d = ((ECPrivateKeyParameters)key).getD();

            s = (k.multiply(e)).add(d.multiply(r)).mod(n);
        }
        while ( s.equals(ECConstants.ZERO) );

        BigInteger[]  res = new BigInteger[2];

        res[0] = r;
        res[1] = s;

        return res;
    }

    /**
     * return true if the value r and s represent a GOST3410 signature for
     * the passed in message (for standard GOST3410 the message should be
     * a GOST3411 hash of the real message to be verified).
     */
    public boolean verifySignature(
        byte[]      message,
        BigInteger  r,
        BigInteger  s)
    {
        byte[] mRev = new byte[message.length]; // conversion is little-endian
        for (int i = 0; i != mRev.length; i++)
        {
            mRev[i] = message[mRev.length - 1 - i];
        }
        
        BigInteger e = new BigInteger(1, mRev);
        BigInteger n = key.getParameters().getN();

        // r in the range [1,n-1]
        if ( r.compareTo(ECConstants.ONE) < 0 || r.compareTo(n) >= 0 )
        {
            return false;
        }

        // s in the range [1,n-1]
        if ( s.compareTo(ECConstants.ONE) < 0 || s.compareTo(n) >= 0 )
        {
            return false;
        }

        BigInteger v = e.modInverse(n);

        BigInteger z1 = s.multiply(v).mod(n);
        BigInteger z2 = (n.subtract(r)).multiply(v).mod(n);

        ECPoint G = key.getParameters().getG(); // P
        ECPoint Q = ((ECPublicKeyParameters)key).getQ();

        ECPoint point = G.multiply(z1).add(Q.multiply(z2));

        BigInteger R = point.getX().toBigInteger().mod(n);

        return R.equals(r);
    }
}
