package aalg.crypto;

import java.math.BigInteger;
import aalg.algebra.IElement;
import aalg.algebra.IGroup;
import aalg.algebra.SubGroupDefinition;

/**
 * Class DsaSigner signs messages using DSA cryptosystem.
 * @author Ing. Petr Ležák
 *
 * @param <T> type of group element
 */
public class DsaSigner<T extends IElement> implements ISigner<DlPrivateKey> {
	
	private final SubGroupDefinition<T> subGroupDefinition;
	private final IHashFunction hashFunction;
	private final IAsymmetricPadding padding;
	private final IRandomGenerator random;
	
	/**
	 * Creates signer.
	 * @param subGroupDefinition definition of subgroup used by the signer; must have prime order
	 * @param hashFunction hash function used for hashing of the message
	 * @param padding padding used for conversion of the hash to number
	 * @param random random number generator
	 */
	public DsaSigner (final SubGroupDefinition<T> subGroupDefinition, final IHashFunction hashFunction, final IAsymmetricPadding padding, final IRandomGenerator random) {
		this.subGroupDefinition = subGroupDefinition;
		this.hashFunction = hashFunction;
		this.padding = padding;
		this.random = random;
		
		if (!subGroupDefinition.getOrder().isProbablePrime(100))
			throw new RuntimeException("Subgroup has compound order");
	}
	
	/**
	 * Signs given message using given private key.
	 * @param message message to sign
	 * @param privateKey private key used for signing
	 * @return signature
	 */
	public DsaSignature sign (final byte[] message, final DlPrivateKey privateKey) {
		final IGroup<T> group = subGroupDefinition.getGroup();
		final BigInteger q = subGroupDefinition.getOrder();
		final BigInteger qMinusOne = q.subtract(BigInteger.ONE);
		
		BigInteger r;
		BigInteger s;
		
		do {
			final BigInteger k = random.nextNumber(BigInteger.ONE, qMinusOne);		
			final T g = subGroupDefinition.getGenerator();
			final T t = group.repeatOperation(g, k);
			System.out.println ("t = " + t);
			
			r = group.getElementIndex(t).mod (q);
			
			final BigInteger invK = k.modInverse(q);
			final BigInteger h = padding.bytesToNumber(hashFunction.calculateHash(message));
			
			s = invK.multiply(h.add(r.multiply (privateKey.getKey()))).mod(q);
		} while (r.equals(BigInteger.ZERO) || s.equals(BigInteger.ZERO));
		
		return new DsaSignature(r, s);
	}
}
