/*
 * Copyright (c) 2001, 2002 X-Way Rights BV
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

/*!\file dsa.c
 * \brief Digital Signature Algorithm.
 * \ingroup DL_m DL_dsa_m
 */
 
#define BEECRYPT_DLL_EXPORT

#if HAVE_CONFIG_H
# include "config.h"
#endif

#include "beecrypt/dsa.h"
#include "beecrypt/dldp.h"

int dsasign(const mpbarrett* p, const mpbarrett* q, const mpnumber* g, randomGeneratorContext* rgc, const mpnumber* hm, const mpnumber* x, mpnumber* r, mpnumber* s)
{
	register size_t psize = p->size;
	register size_t qsize = q->size;

	register mpw* ptemp;
	register mpw* qtemp;

	register mpw* pwksp;
	register mpw* qwksp;

	register int rc = -1;

	ptemp = (mpw*) malloc((5*psize+2)*sizeof(mpw));
	if (ptemp == (mpw*) 0)
		return rc;

	qtemp = (mpw*) malloc((9*qsize+6)*sizeof(mpw));
	if (qtemp == (mpw*) 0)
	{
		free(ptemp);
		return rc;
	}

	pwksp = ptemp+psize;
	qwksp = qtemp+3*qsize;

	/* allocate r */
	mpnfree(r);
	mpnsize(r, qsize);

	/* get a random k, invertible modulo q; store k @ qtemp, inv(k) @ qtemp+qsize */
	mpbrndinv_w(q, rgc, qtemp, qtemp+qsize, qwksp);

	/* g^k mod p */
	mpbpowmod_w(p, g->size, g->data, qsize, qtemp, ptemp, pwksp);

	/* (g^k mod p) mod q - simple modulo */
	mpmod(qtemp+2*qsize, psize, ptemp, qsize, q->modl, pwksp);
	mpcopy(qsize, r->data, qtemp+psize+qsize);

	/* allocate s */
	mpnfree(s);
	mpnsize(s, qsize);

	/* x*r mod q */
	mpbmulmod_w(q, x->size, x->data, r->size, r->data, qtemp, qwksp);

	/* add h(m) mod q */
	mpbaddmod_w(q, qsize, qtemp, hm->size, hm->data, qtemp+2*qsize, qwksp);

	/* multiply inv(k) mod q */
	mpbmulmod_w(q, qsize, qtemp+qsize, qsize, qtemp+2*qsize, s->data, qwksp);

	rc = 0;

	free(qtemp);
	free(ptemp);

	return rc;
}

int dsavrfy(const mpbarrett* p, const mpbarrett* q, const mpnumber* g, const mpnumber* hm, const mpnumber* y, const mpnumber* r, const mpnumber* s)
{
	register size_t psize = p->size;
	register size_t qsize = q->size;

	register mpw* ptemp;
	register mpw* qtemp;

	register mpw* pwksp;
	register mpw* qwksp;

	register int rc = 0;

	/* h(m) shouldn't contain more bits than q */
	if (mpbits(hm->size, hm->data) > mpbits(q->size, q->modl))
		return rc;

	/* check 0 < r < q */
	if (mpz(r->size, r->data))
		return rc;

	if (mpgex(r->size, r->data, qsize, q->modl))
		return rc;

	/* check 0 < s < q */
	if (mpz(s->size, s->data))
		return rc;

	if (mpgex(s->size, s->data, qsize, q->modl))
		return rc;

	ptemp = (mpw*) malloc((6*psize+2)*sizeof(mpw));
	if (ptemp == (mpw*) 0)
		return rc;

	qtemp = (mpw*) malloc((8*qsize+6)*sizeof(mpw));
	if (qtemp == (mpw*) 0)
	{
		free(ptemp);
		return rc;
	}

	pwksp = ptemp+2*psize;
	qwksp = qtemp+2*qsize;

	mpsetx(qsize, qtemp+qsize, s->size, s->data);

	/* compute w = inv(s) mod q */
	if (mpextgcd_w(qsize, q->modl, qtemp+qsize, qtemp, qwksp))
	{
		/* compute u1 = h(m)*w mod q */
		mpbmulmod_w(q, hm->size, hm->data, qsize, qtemp, qtemp+qsize, qwksp);

		/* compute u2 = r*w mod q */
		mpbmulmod_w(q, r->size, r->data, qsize, qtemp, qtemp, qwksp);

		/* compute g^u1 mod p */
		mpbpowmod_w(p, g->size, g->data, qsize, qtemp+qsize, ptemp, pwksp);

		/* compute y^u2 mod p */
		mpbpowmod_w(p, y->size, y->data, qsize, qtemp, ptemp+psize, pwksp);

		/* multiply mod p */
		mpbmulmod_w(p, psize, ptemp, psize, ptemp+psize, ptemp, pwksp);

		/* modulo q */
		mpmod(ptemp+psize, psize, ptemp, qsize, q->modl, pwksp);

		rc = mpeqx(r->size, r->data, psize, ptemp+psize);
	}

	free(qtemp);
	free(ptemp);

	return rc;
}

int dsaparamMake(dsaparam* dp, randomGeneratorContext* rgc, size_t psize)
{
	/* psize must be >= 512 and <= 1024 */
	if ((psize < 512) || (psize > 1024))
		return -1;

	/* psize must be a multiple of 64 */
	if ((psize & 0x3f) != 0)
		return -1;

	return dldp_pgoqMake(dp, rgc, psize, 160, 1);
}
