/*
 This file is part of GNUnet.
  (C) 2012, 2013 Christian Grothoff (and other contributing authors)

  GNUnet is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published
  by the Free Software Foundation; either version 3, or (at your
  option) any later version.

  GNUnet is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with GNUnet; see the file COPYING.  If not, write to the
  Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  Boston, MA 02111-1307, USA.
 */

package org.gnunet.util.crypto;

import org.gnunet.construct.FixedSizeIntegerArray;
import org.gnunet.construct.Message;
import org.gnunet.util.HashCode;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOError;
import java.io.IOException;
import java.math.BigInteger;
import java.security.SecureRandom;

/**
 * Private key for elliptic curve DSA.
 */
public class EcdsaPrivateKey implements Message {
    /**
     * Private key byte string, in big endian form.
     */
    @FixedSizeIntegerArray(bitSize = 8, signed = false, length = 32)
    public byte[] d;

    /**
     * Create a private key without allocating the key data.
     */
    public EcdsaPrivateKey() {
        // empty, needed by org.gnunet.construct.*
    }

    /**
     * Get the anonymous private key.  Corresponds to '1'.
     *
     * @return the anonymous private ECDSA key
     */
    public static EcdsaPrivateKey getAnonymous() {
        EcdsaPrivateKey privateKey = new EcdsaPrivateKey();
        privateKey.d = new byte[32];
        privateKey.d[31] = 1;
        return privateKey;
    }

    public EcdsaSignature sign(byte[] data, int purpose) {
        return sign(data, purpose, getPublicKey());
    }

    public EcdsaSignature sign(byte[] data, int purpose, EcdsaPublicKey publicKey) {
        ByteArrayOutputStream os = new ByteArrayOutputStream(data.length + 8);
        DataOutputStream dos = new DataOutputStream(os);
        try {
            dos.writeInt(data.length);
            dos.writeInt(purpose);
            dos.write(data);
        } catch (IOException e) {
            throw new IOError(e);
        }
        return signRaw(publicKey, os.toByteArray());
    }

    /**
     * Sign the given data with this private key.
     *
     * @param data data to signRaw
     * @return the signature over both the data and the purpose
     */
    public EcdsaSignature signRaw(byte[] data) {
        return signRaw(getPublicKey(), data);
    }

    /**
     * Sign the given data with this private key.
     *
     * @param publicKey public key corresponding to this private key, supplying this parameter
     *                  leads to better performance as the public key does not have to be derived
     * @param data data to signRaw
     * @return the signature over both the data and the purpose
     */
    public EcdsaSignature signRaw(EcdsaPublicKey publicKey, byte[] data) {
        EcdsaSignature signature = new EcdsaSignature();
        DsaPrng prng = new DsaPrng(d, data);
        HashCode h = HashCode.hash(data);
        BigInteger z = new BigInteger(1, h.data);
        BigInteger dCoeff = this.asCoefficient();

        while (true) {
            BigInteger k = prng.nextK();
            Ed25519 P = Ed25519.B.scalarmult(k);
            BigInteger r = P.P0.mod(Ed25519.l);
            if (r.equals(BigInteger.ZERO))
                continue;
            BigInteger kInv = k.modInverse(Ed25519.l);
            BigInteger v = z.add(r.multiply(dCoeff));
            BigInteger s = kInv.multiply(v).mod(Ed25519.l);
            if (s.equals(BigInteger.ZERO))
                continue;
            signature.r = Ed25519.encodeScalar(r);
            signature.s = Ed25519.encodeScalar(s);
            return signature;
        }
    }

    /**
     * Get the public key for this private key.
     *
     * @return the public key for this private key
     */
    public EcdsaPublicKey getPublicKey() {
        Ed25519 A = Ed25519.B.scalarmult(this.asCoefficient());
        return new EcdsaPublicKey(A);
    }

    /**
     * Create a randomly generated private ecdsa key.
     *
     * @return a freshly generated key
     */
    public static EcdsaPrivateKey createRandom() {
        SecureRandom sr = new SecureRandom();
        EcdsaPrivateKey privateKey = new EcdsaPrivateKey();
        privateKey.d = new byte[32];
        sr.nextBytes(privateKey.d);
        return privateKey;
    }

    public BigInteger asCoefficient() {
        return new BigInteger(1, d);
    }

    /**
     * Key that is set to all zeros.
     *
     * @return key that is set to all zeros
     */
    public static EcdsaPrivateKey zeroKey() {
        EcdsaPrivateKey pk = new EcdsaPrivateKey();
        pk.d = new byte[32];
        return pk;
    }
}
