/*
 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 com.google.common.primitives.Bytes;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

/**
 * Deterministic generator for the 'k'-value of DSA, conforming to RFC 6979.
 * SHA-512 is used as H.
 */
public class DsaPrng {
    private Mac mac;
    private byte[] V = new byte[64];
    private byte[] K = new byte[64];
    private static final int qlen = 32;

    /**
     * Compute the HMAC of the given data with our current K-value.
     *
     * @param args data
     * @return result of the hmac
     */
    private byte[] hmacK(byte[]... args) {
        try {
            mac.init(new SecretKeySpec(K, "HmacSHA1"));
        } catch (InvalidKeyException e) {
            throw new RuntimeException("invalid key: " + e.getMessage());
        }
        for (byte[] bytes : args) {
            mac.update(bytes);
        }
        return mac.doFinal();
    }

    /**
     * Create an instance of the deterministic random number generator for the DSA 'k' vale.
     *
     * @param key private key
     * @param message message
     */
    public DsaPrng(byte[] key, byte[] message) {
        try {
            mac = Mac.getInstance("HmacSHA1");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("crypto algorithm 'HmacSHA1' required but not provided");
        }
        MessageDigest digest;
        try {
            digest = MessageDigest.getInstance("SHA-512");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("crypto algorithm 'SHA-512' required but not provided");
        }
        byte[] h1 = digest.digest(message);

        Arrays.fill(V, (byte) 1);
        K = hmacK(V, new byte[]{0}, key, h1);
        V = hmacK(V);
        K = hmacK(V, new byte[]{1}, key, h1);
        V = hmacK(V);
    }

    /**
     * Get the next deterministically generated candidate for
     * the k value used in DSA.
     *
     * @return the next candidate value for 'k'
     */
    public BigInteger nextK() {
        byte[] T = new byte[0];
        while (T.length < qlen) {
            V = hmacK(V);
            T = Bytes.concat(T, V);
        }
        K = hmacK(V, new byte[]{0});
        V = hmacK(V);
        return new BigInteger(1, T);
    }
}
