/*
 *  Algebraic operations on the finite field GF(2^m)
 *
 *
 * 
 * 	Copyright (C) 2004-2007  Manuel Pancorbo Castro
 * 
 *	This program 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 2
 *	of the License, or (at your option) any later version.
 * 
 *	This program 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 this program; if not, write to the Free Software
 *	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 *	Manuel Pancorbo Castro <mpancorbo@gmail.com>
 * 
 * 
 */

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define _GFLIB_C
#include "gflib.h"

static gf_poly R;
static int GF_Init = 0;

//#if (defined FULLTABLES) && ((GF_DIGIT_BIT)==28)
//	#define GF_SQR_TABLE_SIZE (1<<14)
//	gf_unit  sqr[GF_SQR_TABLE_SIZE];
//#else
#define GF_SQR_TABLE_SIZE (1 << 8)
static unsigned short int  sqr[GF_SQR_TABLE_SIZE];
//#endif

#if !(defined GF_TM1) && (defined FULLTABLES)

static _gf_poly quad[GF_M];

#endif

static int tables_builded = 0;

static void build_tables(void)
{
	gf_unit i, ii, jj;
	gf_unit c;
	gf_poly a;
	
	gf_unit lim = GF_SQR_TABLE_SIZE;
	
	for(i = 0; i < lim; ++i){
		/** Sqr **/
		jj = 1, ii = i, c = 0;
		while(ii){
			if(ii & 1) c ^= jj;
			jj <<= 2;
			ii >>= 1;
		}
		sqr[i] = c;
	}
#if !(defined GF_TM1) && (defined FULLTABLES)
	/* QuadSolve quick table */
	tables_builded = 1;
	gf2m_Set(a, 1);
	for(i = 0; i < GF_M; ++i){
		if(i == GF_TM0)	gf2m_Zero(&quad[i]);
		else gf2m_SlowQuadSolve(&quad[i],a);
		
		gf2m_Mul_x(a,a);
	}
	
#endif
}

/*
gf_poly gf2m_Open()
{
	gf_poly a;
	
	if( (a = malloc(sizeof(mp_int))) == NULL ) return NULL;
	if(mp_init_size(a, 2*GF_M / GF_DIGIT_BIT + 4)) return NULL;
	return a;
}
*/
ERROR gf2m_Init(void)
/* Sets global 'R' with reduction trinomial */
/* Sets auxiliar variables */
{
	if(GF_Init) return 0;
	GF_Init = 1;
	//gfOpen(R);
	if(R == NULL) return 1;
	gf2m_Set(R, 1);
	gf2m_Mul_xn(R, GF_M - GF_T, R);
	fp_add_d(R, 1, R);
	gf2m_Mul_xn(R, GF_T, R);
	fp_add_d(R, 1, R);
	
		
	if(!tables_builded){
		build_tables();

		tables_builded = 1;
	}
	
	
	return 0;
}

void gf2m_Quit(void)
{

	//gfClear(R);
	
	GF_Init = 0;
}

int gf2m_Deg(gf_poly a)
{
	register int deg;
	register gf_unit c;
	int m = a->used;
	
	assert(a != NULL);
	//assert(gfExists(a));
	if(gf_iszero(a)) return -1;
	/*while(!DIGIT(a, m - 1)) m--;*/
	c = a->dp[m-1];
	deg = (m-1) * GF_DIGIT_BIT;
	while(c){
		++deg;
		c >>= 1;
	}
	return deg - 1; 

}

#if 0
void gf2m_Clear(gf_poly a)
{	
	return;
	//assert( a != NULL );
	//assert(gfExists(a));
	//gf2m_Zero(a); 
	//mp_clear(a);
	//free(a);
}
#endif

void gf2m_Pack(gf_poly a, gfPoint b)
{
	memset(b, 0, GF_SIZE);
	if (gf_iszero(a)) return;
	gfPut(a, b + GF_SIZE - gfSize(a));

}

void gf2m_SmallAdd(gf_poly a, gf_unit b)
{
	assert( (a != NULL) );
	//assert( gfExists(a) );
	
	if(!b) return;
	
	if(gf_iszero(a)){
		*(a->dp) ^= b;
		a->used++;
	}
	else{
		*(a->dp) ^= b;
		gf_clamp(a);
	}
}

void gf2m_Add(gf_poly c, gf_poly a, gf_poly b)
{
	int  ix, px;
	_gf_poly *x;
	gf_poly t;
	
	if (a->used > b->used) {
		gfCopy(t, a);
		px = b->used;
    	x = b;
    } else {
    	gfCopy(t, b);
		px = a->used;
    	x = a;
    }
    for (ix = 0; ix < px; ix++) {
    	t->dp[ix] ^= x->dp[ix];
  	}
  	gf_clamp (t);
  	gfCopy(c, t);
    
}


//#if (sizeof(fp_digit) > GF_T)
inline void gf2m_Reduce_Small_T(gf_poly p)
	/* reduces p mod the irreducible trinomial X^191 + X^9 + 1 */
{
	assert(GF_Init);
	assert(p != NULL);
	//assert(gfExists(p));
	
	const int digit_offset = GF_M/GF_DIGIT_BIT;
	const int bit_offset_M0 = GF_M % GF_DIGIT_BIT;
	const int bit_offset_MT = (GF_M-GF_T) % GF_DIGIT_BIT;
	register gf_unit val, *ptr /*, mask*/;
	gf_word mask;
	
	ptr = &(p->dp[p->used-1]);
	
	while(p->used > digit_offset + 1){
		if((val = *ptr)){
		/*
			mask = (val >> bit_offset_MT);
			mask ^= (val >> bit_offset_M0);
			*(ptr - digit_offset) ^= mask;
		*/
			mask = ((gf_word) val << (GF_DIGIT_BIT - bit_offset_MT));
			mask ^= ((gf_word) val << (GF_DIGIT_BIT - bit_offset_M0));
			//mask &= GF_MASK;
			*(ptr - digit_offset -1) ^= (gf_unit) (mask & GF_MASK);
			*(ptr - digit_offset) ^= (gf_unit) (mask >> GF_DIGIT_BIT);
			*ptr = 0;
		}
		ptr--;
		p->used--;
	}
	
	if(p->used == digit_offset + 1){
		mask = (((gf_unit)1 << bit_offset_M0) - 1) ^ GF_MASK;
		if((val = *ptr & mask)){
			*ptr &= (mask ^ GF_MASK);
			mask = (val >> bit_offset_MT);
			mask ^= (val >> bit_offset_M0);
			*(ptr - digit_offset) ^= mask;
		}
	}
	while(!(*ptr) && p->used){ptr--, p->used--;}	
}

//#elif (GF_T % GF_DIGIT_BIT == 0)
void gf2m_Reduce_Congruent_T(gf_poly p)
	/* reduces p mod the irreducible trinomial X^191 + X^(GF_DIGIT_BIT*n) + 1 */
{
	assert(GF_Init);
	assert(p != NULL);
	//assert(gfExists(p));

	const int digit_offset_M0 = GF_M/GF_DIGIT_BIT;
	const int digit_offset_MT = (GF_M-GF_T) / GF_DIGIT_BIT;
	const int bit_offset_M0 = GF_M % GF_DIGIT_BIT;
	
	register gf_unit val, *ptr, mask;
	
	ptr = &(p->dp[p->used-1]);
	while(p->used > digit_offset_M0 + 1){
		if((val = *ptr)){
			mask = (val >> bit_offset_M0);
			*(ptr - digit_offset_M0) ^= mask;
			*(ptr - digit_offset_MT) ^= mask;
			mask = (val << (GF_DIGIT_BIT - bit_offset_M0)) & GF_MASK;
			*(ptr - digit_offset_M0 -1) ^= mask;
			*(ptr - digit_offset_MT -1) ^= mask;
			*ptr = 0;
		}
		ptr--;
		p->used--;
	}
	if(p->used == digit_offset_M0 + 1){
		mask = (((gf_unit)1 << bit_offset_M0) - 1) ^ GF_MASK;
		if((val = *ptr & mask)){
			*ptr &= (mask ^ GF_MASK);
			mask = (val >> bit_offset_M0);
			*(ptr - digit_offset_M0) ^= mask;
			*(ptr - digit_offset_MT) ^= mask;
			
		}
	}
	while(!(*ptr) && p->used){ptr--, p->used--;}	
}
//#elif (GF_M - GF_T > GF_DIGIT_BIT)
void gf2m_Reduce_General(gf_poly p)
	/* reduces p mod the irreducible trinomial X^191 + Y^T + 1 */
{
	assert(GF_Init);
	assert(p != NULL);
	//assert(gfExists(p));

	const int digit_offset_M0 = GF_M/GF_DIGIT_BIT;
	const int digit_offset_MT = (GF_M-GF_T) / GF_DIGIT_BIT;
	const int bit_offset_M0 = GF_M % GF_DIGIT_BIT;
	const int bit_offset_MT = (GF_M-GF_T) % GF_DIGIT_BIT;
	
	register gf_unit val, *ptr, mask;
	
	ptr = &(p->dp[p->used-1]);
	while(p->used > digit_offset_M0 + 1){
		if((val = *ptr)){
			mask = (val >> bit_offset_M0);
			*(ptr - digit_offset_M0) ^= mask;
			mask = (val >> bit_offset_MT);
			*(ptr - digit_offset_MT) ^= mask;
			mask = (val << (GF_DIGIT_BIT - bit_offset_M0)) & GF_MASK;
			*(ptr - digit_offset_M0 -1) ^= mask;
			mask = (val << (GF_DIGIT_BIT - bit_offset_MT)) & GF_MASK;
			*(ptr - digit_offset_MT -1) ^= mask;
			*ptr = 0;
		}
		ptr--;
		p->used--;
	}
	if(p->used == digit_offset_M0 + 1){
		mask = (((gf_unit)1 << bit_offset_M0) - 1) ^ GF_MASK;
		if((val = *ptr & mask)){
			*ptr &= (mask ^ GF_MASK);
			mask = (val >> bit_offset_M0);
			*(ptr - digit_offset_M0) ^= mask;
			mask = (val >> bit_offset_MT);
			*(ptr - digit_offset_MT) ^= mask;
			
		}
	}
	while(!(*ptr) && p->used){ptr--, p->used--;}	
}

void gf2m_Reduce(gf_poly p)
{
	if(GF_T < GF_DIGIT_BIT) gf2m_Reduce_Small_T(p);
	else if(GF_T % GF_DIGIT_BIT == 0) gf2m_Reduce_Congruent_T(p);
	else if(GF_M - GF_T > GF_DIGIT_BIT) gf2m_Reduce_General(p);
	return;
}

//#else
//	#error "Unsupported trinomial"

//#endif



#if 0
inline gf_unit gf2m_HalfSmallMult(gf_unit a, gf_unit b)
{
	register gf_unit c = 0;
	register gf_unit mdor, mando;
	
	mdor = (a < b) ? a : b;
	mando = ( (a ^ b)) ^ mdor;
	
	while(mdor){
		if(mdor & (gf_unit) 1) c ^= mando;
		mdor >>= 1;	
		mando <<= 1;
	}
	
	return c;
}

inline gf_word gf2m_SmallMult(gf_unit a, gf_unit b)
	/*** returns a*b ***/
{
	//if(!a || !b) return (gf_word) 0;
	
	gf_unit lo_a, hi_a, lo_b, hi_b;
	static gf_unit shift2 = sizeof(gf_unit)/2;
	//static gf_unit shift = sizeof(gf_unit);
	static gf_unit mask = (1 << sizeof(gf_unit)/2) - 1;
	gf_unit lo_c, hi_c;
	gf_word c;
	
	lo_a = a & mask, lo_b = b & mask;
	hi_a = a >> shift2, hi_b = b >> shift2;

	lo_c = gf2m_HalfSmallMult(lo_a, lo_b);
	hi_c = gf2m_HalfSmallMult(hi_a, hi_b);
	c = (gf_word) hi_c << (gf_word) shift2;
	c ^= gf2m_HalfSmallMult(hi_a^lo_a, hi_b^lo_b)^lo_c^hi_c;
	c <<= (gf_word) shift2;
	c ^= (gf_word)lo_c;
	
	return c;
}
#endif

inline gf_word gf2m_SmallMult(gf_unit a, gf_unit b)
	/*** returns a*b ***/
{
	gf_word c = 0;
	gf_word mdor, mando;
	
	mdor =(gf_word) (a < b) ? a : b;
	mando = ((gf_word) (a ^ b)) ^ mdor;
	
	while(mdor){
		if(mdor & (gf_word) 1) c ^= mando;
		mdor >>= 1;	
		mando <<= 1;
	}
	
	return c;
}



void _gf2m_Multiply (gf_poly s, /*const*/ gf_poly p, /*const*/ gf_poly q)
	/** Sets s = p*q (no reduction) **/
	/** It computes products of size GF_DIGIT_BIT **/
{
	
	int deg_p, deg_q, sum;
	register int i, j, k;
	gf_word c, d[2*GF_M/GF_DIGIT_BIT+3];
	gf_poly r;
		
	assert( (s != NULL) && (p != NULL) && (q != NULL) );
	//assert(gfExists(s) && gfExists(p) && gfExists(q));	
	while(p->used < q->used) p->dp[p->used++] = 0;
	while(q->used < p->used) q->dp[q->used++] = 0;
	deg_p = p->used;
	deg_q = q->used;
	sum = deg_p + deg_q - 1;
	
	//gfOpen(r);
	gf2m_Zero(r);
	memset(d, 0, sizeof(d));
	for(k = 0; k < sum; ++k){
		if(k < deg_p && k < deg_q)
			d[k] = gf2m_SmallMult(p->dp[k], q->dp[k]);
		c = 0;
		i = k - deg_q + 1;
		for(i = ((i < 0)? 0:i), j = k - i; (i < deg_p) && (j >= i); ++i, --j){
			if(i == j){
				 c ^= d[i];
			}
			else {
				c ^= gf2m_SmallMult(p->dp[i] ^ p->dp[j], q->dp[i] ^ q->dp[j])^d[i]^d[j];
			}
		}
		
		r->dp[k] ^= (c & GF_MASK);
		r->dp[k+1] ^= (c >> (gf_unit)GF_DIGIT_BIT);
		r->used += 2;
		/*gf_clamp(r);*/
	}
	r->used++; 
	gf_clamp(r);
	gfCopy(s, r);
	gfClear(r);
	gf_clamp(p);
	gf_clamp(q);
}

void gf2m_Multiply (gf_poly r, /*const*/ gf_poly p, /*const*/ gf_poly q)
	/** Sets r = p*q (mod R) **/
{
	_gf2m_Multiply (r, p, q);
	
	gf2m_Reduce(r);
}

#if 0
void _gf2m_Square(gf_poly r, /*const */ gf_poly p)
	/* sets r = p^2 No reduction */
	/* only if GF_DIGIT_BIT == 28 **/
{
	register int i;
	register gf_unit c;
	const gf_unit mask = (1 << 14) - 1;
	gf_poly s;
	
	assert( (r != NULL) && (p != NULL) );
	//assert(gfExists(r) && gfExists(p));
	if(!tables_builded){
		build_tables();
		
		tables_builded = 1;
	}
	//gfOpen(s);
	gfZero(s);
	for(i = 0; i < p->used; ++i){
		c = p->dp[i];
		s->dp[s->used++] = sqr[c & mask];
		s->dp[s->used++] = sqr[c >> 14];
	}
	s->used++; gf_clamp(s);
	gfCopy(r, s); 
	//gfClear(s);
}

#else

void _gf2m_Square(gf_poly r, /*const */ gf_poly p)
{
	register int i, j;
	register gf_unit c;
	int k, t;
	gf_word d;
	gf_poly s;
	
	assert( (r != NULL) && (p != NULL) );
	//assert(gfExists(r) && gfExists(p));
	if(!tables_builded){
		build_tables();
		
		tables_builded = 1;
	}
	//gfOpen(s);
	gfZero(s);
	for(i = k = 0; i < p->used; ++i, k += 2){
		c = p->dp[i];
		
		d = 0;
		j = 0;
		//fprintf(stderr, "Segmento %d:\t%lX\n", i, c);
		while(c){
			t= sqr[(int)(c & 0xff)];
			//fprintf(stderr, "%04x ", t);
			d ^= ((gf_word) t) << j;
			j += 16;
			//d <<= 16;
			c >>= 8;
			c &= GF_MASK;
		}
		//fprintf(stderr, "\n");
		s->dp[k] ^= (d & GF_MASK);
		s->dp[k+1] ^= (d >> (gf_unit)GF_DIGIT_BIT);
		s->used += 2;
	}
	s->used++; gf_clamp(s);
	gfCopy(r, s); 
	gfClear(s);
}


#endif

void gf2m_Square(gf_poly r, /*const */ gf_poly p)
	/* sets r = p^2 (mod R) */
{
	_gf2m_Square(r, p);
	gf2m_Reduce(r);
}


/*** New inversion algorithm (a bit faster than previous ***/
ERROR gf2m_Divide (gf_poly g1,/* const */ gf_poly p, /* const */ gf_poly a)
	/* sets b := (p / a) mod (x^GF_M + x^GF_T + 1) */
	/* warning: a, b and p must not overlap! */
	/* 'p' can be null. Then, it will be performed a^-1 mod R */
{
	assert (GF_Init != 0);	
	assert( (g1 != NULL) && (a != NULL) );
	//assert(gfExists(b) && gfExists(a));
	assert ((g1 != a) && (p != a));
	
	if(gf_iszero(a)) return 1;
	
	const int M_digit_offset = (GF_M - 1)/ GF_DIGIT_BIT,
			  T_digit_offset = (GF_T - 1)/ GF_DIGIT_BIT,
			  M_bit_offset = (GF_M - 1) % GF_DIGIT_BIT,
			  T_bit_offset = (GF_T - 1) % GF_DIGIT_BIT;
	const gf_unit lo = ((gf_unit)1 << T_bit_offset);
	const gf_unit hi = ((gf_unit)1 << M_bit_offset);
	int flag;
	/*register*/ gf_poly u, v, g2/*, g1*/; 
	
	//g1[0] = b[0] /** Alias of b **/;
	//gfOpen(g2); gfOpen(u); gfOpen(v);
	
	/* initialize g1 := p; g2 := 0; u := a; v := x^GF_M + x^GF_T + 1: */
	if(p == NULL){ /** Interprets 'only inversion of a' **/
		gf2m_Set(g1, 1);
	}
	else {
		gfCopy(g1, p);
	}
	gf2m_Zero(g2);
	gf2m_Copy(u, a), gf2m_Copy(v, R);
	/*int count = 0;*/

	while( (gf2m_CmpOne(u) != GF_EQ) && (gf2m_CmpOne(v) != GF_EQ)){
		while(!gf_one(u)){
			gf2m_Div_x(u, u);
			flag = gf_one(g1);
			gf2m_Div_x(g1, g1);
			if(flag){
				g1->dp[M_digit_offset] ^= hi;	
				g1->dp[T_digit_offset] ^= lo;
				g1->used = M_digit_offset + 1;
			}
		}
		while(!gf_one(v)){
			gf2m_Div_x(v, v);
			flag = gf_one(g2);
			gf2m_Div_x(g2, g2);
			if(flag){
				g2->dp[M_digit_offset] ^= hi;	
				g2->dp[T_digit_offset] ^= lo;
				g2->used = M_digit_offset + 1;
			}
		}
		if(gf2m_Comp(u, v) == GF_GT){
			gf2m_Add(u, u, v);
			gf2m_Add(g1, g1, g2);
		}
		else{
			gf2m_Add(v, u, v);
			gf2m_Add(g2, g1, g2);
		}
	}
	if(gf2m_CmpOne(v) == GF_EQ) gf2m_Copy(g1, g2);
	gfClear(g2), gfClear(u), gfClear(v);
	
	return 0;
	
}

void gf2m_SquareRoot (gf_poly p, gf_unit b)
	/* sets p := sqrt(b) = b^(2^(GF_M-1)) */
{
	register int i;
	gf_poly q;
	
	assert(p != NULL);
	//assert(gfExists(p)); 
	assert (GF_Init != 0);
	if(!b){
		gf2m_Zero(p);
		return;
	}
	
	//gfOpen(q);
	
	gf2m_Set(p, b);
	i = GF_M - 1;
	while (i) {
		gf2m_Square (q, p);
		gf2m_Square (p, q);
		i -= 2;
	}
	gfClear(q);
} /* gf2m_SquareRoot */

#ifndef TRACE_MASK
int gf2m_Trace(/*const*/ gf_poly p)
{
	int tr = 0;
	gf_poly t;

	assert (GF_Init != 0);
	assert( p != NULL );
	//assert(gfExists(p)); 
	//gfOpen(t);
	
	//mp_div_2d(p, GF_TM0, t, NULL);
	gf2m_Div_xn(t, GF_TM0, p);
	
	tr ^= gf_one(t);
	#ifdef GF_TM1
	while(!gf_iszero(t)){
		//mp_div_2d(t, GF_TM1, t, NULL);
		gf2m_Div_xn(t, GF_TM1, t);
		tr ^= gf_one(t);
	}
	#endif
	gfClear(t);
	return tr;
}  /* gf2m_Trace */



ERROR gf2m_SlowQuadSolve (gf_poly p, /*const*/ gf_poly beta)
	/* sets p to a solution of p^2 + p = beta 
		Slow version */
{
#if 0
	assert (GF_Init != 0);
	assert( (beta != NULL) && (p != NULL) );
	//assert(gfExists(beta) && gfExists(p));
	assert (p != beta);
	if (gfTrace (beta) != 0) {
		return 1; /* no solution */
	}
#endif	
	register int i;
	
	gf2m_Copy (p, beta);
	for (i = 0; i < GF_M/2; i++) {
		gf2m_Square (p, p);
		gf2m_Square (p, p);
		gf2m_Add (p, p, beta);
	}
	return 0;
} /* gfQuadSolve */


ERROR gf2m_QuadSolve (gf_poly p, /*const*/ gf_poly beta)
	/* sets p to a solution of p^2 + p = beta 
		Quick version. It uses precalculated values of
		beta = x^i */
{

	assert (GF_Init != 0);
	assert( (beta != NULL) && (p != NULL) );
	assert (p != beta);
	if (gfTrace (beta) != 0) {
		return 1; /* no solution */
	}
	
#if !(defined GF_TM1) && (defined FULLTABLES)
	register int i;
	int j;
	register gf_unit bit_check = 1;
	
	gf2m_Zero(p);
	for(i = 0; i < GF_DIGIT_BIT; ++i){
		for(j = 0; j < beta->used; ++j){
			if(bit_check & beta->dp[j])
				gf2m_Add(p, p, quad+GF_DIGIT_BIT*j+i);
		}
		bit_check <<= 1;
	}
	return 0;
#else
	return gf2m_SlowQuadSolve (p, beta);
#endif /* GF_TM1 - FULLTABLES */
}

#endif /* ?TRACE_MASK */

void gf2m_Random(gf_poly a)
	/* Only for testing purposes */
{
	int i, shift;
	unsigned int rnd;
	
	assert( a != NULL);
	//assert(gfExists(a)); 
	gf2m_Zero(a);
	
	shift = (sizeof(rnd) > GF_DIGIT_BIT)? GF_DIGIT_BIT: sizeof(rnd);
	for(i = 0; i < GF_M/shift + 1; ++i){
		gf2m_Mul_xn(a, shift, a);
		//mp_mul_2d(a, shift, a);
		rnd = rand();
		fp_add_d(a, rnd & GF_MASK, a);
	}
	gf2m_Reduce(a);
}

void gf2m_Print (FILE *fOut, /*const*/ char *tag, /*const*/ gf_poly p)
	/* prints tag and the contents of p to file fOut */
{
	char buf[256];
	fp_toradix(p, buf,16);
	fprintf (fOut, "%s = %s\n", tag, buf);

}

#if defined GFLIB_TEST || defined TRACE_MASK

int gf2m_SlowTrace (/*const*/ gf_poly p)
	/* slowly evaluates to the trace of p (or an error code) */
{
	int i;
	gf_poly t;

	assert (GF_Init != 0);
	//gfOpen(t);
	gf2m_Copy (t, p);
	for (i = 1; i < GF_M; i++) {
		gf2m_Square (t, t);
		gf2m_Add (t, t, p);
	}

	i = (t->used != 0);
	
	gfClear(t);
	return i;
} /* gfSlowTrace */

#endif

#ifdef GFLIB_TEST

main(int argc, char ** argv)
{

	int i, test_count
		,afail = 0
		,mfail = 0
		,dfail = 0
		,sfail = 0
		,ifail = 0
//		,kfail = 0
		,rfail = 0
		,tfail = 0
		,qfail = 0
		,gfail = 0
		,dvfail = 0
		;
	gf_poly f, g, h, x, y, z;
	gf_unit b;
	clock_t elapsed, elap_quad = 0L, elap_mul = 0, //elap_kar = 0, 
			elap_inv = 0, //elap_inv2 = 0, 
			elap_sqr = 0;
	gf_word TOGGLE = ((gf_word)1 << GF_DIGIT_BIT) - 1;
	
	if(argc > 1){
		test_count = atoi(argv[1]);
	}
	else test_count = 10;
	
	printf("Bits por dígito %d\n", GF_DIGIT_BIT);
	printf("Máscara %ld\n", GF_MASK);
	printf("Tamaño de dígito %d\n", sizeof(gf_unit));
	printf("Tamaño de doble %d\n", sizeof(gf_word));
	gfInit();
	gfPrint(stderr, "R", R);
	
	srand ((unsigned)(time(NULL) % 65521U));
	printf ("Executing %d field self tests.", test_count);
	/*printf("Parameters:\n Curve: %d\nField (2^%d)(2^%d)\n", LaCurva, GF_L, GF_K);*/
	printf("Bits: %d\nTrinomial: %d\n", GF_M, GF_T);
	//gfOpen(f); gfOpen(g); gfOpen(h);
	//gfOpen(x); gfOpen(y); gfOpen(z);
	elapsed = -clock ();
	
	for (i = 0; i < test_count; i++) {
		gfRandom (f);
		gfRandom (g);
		gfRandom (h);
		
		/* addition test: f+g = g+f */
		gfAdd (x, f, g); 
		gfAdd (y, g, f);
		
		/*gfPrint(stderr, "sumando 1:\n", f);
		gfPrint(stderr, "sumando 2:\n", g);
		gfPrint(stderr, "resultado:\n", x);*/
		if (!gfEqual (x, y)) {
			afail++;
			/* printf ("Addition test #%d failed!\n", i); */
		}
		
		/* multiplication test: f*g = g*f */
		/*mp_set(f, 1);
		gf_clamp(f);*/
		
		
		elap_mul -= clock();
		gf2m_Multiply (x, f, g);
		gf2m_Multiply (y, g, f);
		elap_mul += clock();
		if (!gfEqual (x, y)) {
			fprintf(stderr, "Tamaño gf_word = %d\n", sizeof(gf_word));
			gfPrint(stderr, "Verdadero", x);
			gfPrint(stderr, "Prueba   ", y);
			
			mfail++;
			/*fprintf(stderr, "Elementos f: %d\n\n", f->K);
			fprintf(stderr, "Elementos g: %d\n\n", g->K);*/
			/* printf ("Multiplication test #%d failed!\n", i); */
			/*fprintf(stderr, "Prueba de multiplicar: %lx x %lx = %llx\n",
			b = (1 << GF_DIGIT_BIT ) - 1;
				1, b, gf2m_SmallMult(b, 1));*/
		}
		
		/* distribution test: f*(g+h) = f*g + f*h */
		elap_mul -= clock();
		gfMultiply (x, f, g);
		gfMultiply (y, f, h);
		elap_mul += clock();
		gfAdd (y, x, y);
		gfAdd (z, g, h);
		elap_mul -= clock();
		gfMultiply (x, f, z);
		elap_mul += clock();
		if (!gfEqual (x, y)) {
			dfail++;
			/*gfPrint(stderr, "f", f);
			gfPrint(stderr, "g", g);
			gfPrint(stderr, "h", h);
			gfPrint(stderr, "f*(g+h)", x);
			fprintf(stderr, "Elementos: %d\n\n", x->K);
			gfPrint(stderr, "f*g+f*h", y);
			fprintf(stderr, "Elementos: %d\n\n", y->K);
			return -1; */
			/* printf ("Distribution test #%d failed!\n", i); */
		}
		
		/* karatsuba mul. test */
		#if 0
		elap_mul -= clock();
		gfMultiply (x, f, g);
		elap_mul += clock();
		elap_kar -= clock();
		gf2m_KaratsubaMul(y, f, g);
		gfReduce(y);
		elap_kar += clock();
		if (!gfEqual (x, y)) {
			kfail++;
			/*gfPrint(stderr, "f", f);
			gfPrint(stderr, "g", g);
			gfPrint(stderr, "h", h);
			gfPrint(stderr, "f*(g+h)", x);
			fprintf(stderr, "Elementos: %d\n\n", x->K);
			gfPrint(stderr, "f*g+f*h", y);
			fprintf(stderr, "Elementos: %d\n\n", y->K);
			return -1; */
			/* printf ("Distribution test #%d failed!\n", i); */
		}
		#endif
		
		/* squaring test: f^2 = f*f */
		gfZero(x), gfZero(y);
		gfSet(f, 0xA234567);
		gf2m_Mul_xn(f, 100, f);
		
		gfReduce(f);
		elap_sqr -= clock();
		_gf2m_Square (x, f);
		gfReduce(x);
		elap_sqr += clock();
		
		elap_mul -= clock();
		_gf2m_Multiply (y, f, f);
		gfReduce(y);
		elap_mul += clock();
				
		if (!gfEqual (x, y)) {
			sfail++;
			gfPrint(stderr, "f", f);
			gfPrint(stderr, "f^2", x);
			gfPrint(stderr, "f*f", y);
			fprintf(stderr, "Número de dígitos: %d\n", x->used);
			fprintf(stderr, "Último dígito: %lx\n", y->dp[y->used-1]);
			fprintf(stderr, "Siguiente dígito: %lx\n", y->dp[y->used]);
			/*return -1;*/
			/* printf ("Squaring test #%d failed:\n", i); */
		}
		
		/** inversion test: g*(f/g) = f **/
		if (!gf_iszero(g)) {
			/*gf2m_Set(g, 2);*/
			gfReduce(g); 
			elap_inv -= clock();
			gf2m_Divide (x, f, g); /* x = 1/g */
			elap_inv += clock();
			//elap_inv2 -= clock();
			//gf2m_Divide2 (y, f, x); /* y = f/x = f*g/f = g */
			//elap_inv2 += clock();
			/*gfReduce(f);*/
			/*fprintf(stderr, "Grado de f: %d\n", gfDeg(f));*/
			
			gfMultiply(y, g, x); /* y= x*g =? f */
			if (!gfEqual (f, y)) {
			
				gfPrint(stderr, "Dividendo, f", f);
				gfPrint(stderr, "Divisor, g", g);
				gfPrint(stderr, "Cociente, x", x);
				gfPrint(stderr, "g·x = f?", y);
				ifail++;
			
				printf ("Inversion test #%d failed!\n", i); 
			}
		}
		#if 0
		/** new square algorithm test **/
		mp_div_2d(g, GF_M/2, g, NULL);
		gfSquare(x, g);
		_gf2m_Square_new(y, g);
		gfReduce(y);
		if(!gfEqual (x, y)) {
			gfPrint(stderr, "Original", g);
			gfPrint(stderr, "Cuadrado      ", x);
			gfPrint(stderr, "Nuevo cuadrado", y);
			return -1;
		}
		#endif
		/** square root test: sqrt(b)^2 = b */
		b =  rand () & TOGGLE;
		gf2m_Zero(z);
		if (b) {
			gf2m_Set(z, b);
		} else {
			gf2m_Zero(z);
		}
		gfSquareRoot (y, b);
		gfSquare (x, y);
		//_gf2m_Square_old(x, y);
		//gf2m_Reduce(x);
		
		if (!gfEqual (x, z)) {
			rfail++;
			gfPrint(stderr, "Original", z);
			gfPrint(stderr, "Raiz cuadrada", y);
			fprintf(stderr, "Grado: %d\n", gfDeg(y));
			gfPrint(stderr, "y*y", x);
			return -1;
			/* printf ("Square root test #%d failed!\n", i); */
		}
		
		/* trace test: slow tr(f) = tr(f) */
		if (gfTrace (f) != gfSlowTrace (f)) {
			tfail++;
			/* printf ("Trace test #%d failed!\n", i); */
		}
		
		/** quadratic equation solution test: x^2 + x = f (where tr(f) = 0)*/
		
		if (gfTrace (f) == 0) {
			/*fprintf(stderr, "Traza no nula\n");*/
			elap_quad -= clock ();
			gfQuadSolve (x, f);
			elap_quad += clock ();
			elap_sqr -= clock();
			gfSquare (y, x);
			elap_sqr += clock();
			gfAdd (y, y, x);
			if (!gfEqual (y, f)) {
				qfail++;
				/* printf ("Quadratic equation test #%d failed!\n", i); */
			}
		}
	}
	elapsed += clock ();
	
	gfQuit();
	                   
	
	printf (" done, elapsed time = %.2f s.\n", (float)elapsed/CLOCKS_PER_SEC);
	printf (" multiplication time = %.4f s.\n", (float)elap_mul/CLOCKS_PER_SEC/7.);
	//printf (" karatsuba mult. time = %.4f s.\n", (float)elap_kar/CLOCKS_PER_SEC);
	printf (" squaring time = %.4f s.\n", (float)elap_sqr/CLOCKS_PER_SEC/2.);
	
	printf (" quad- solving time = %.4f s.\n", (float)elap_quad/CLOCKS_PER_SEC);
	
	printf (" inversion time = %.4f s.\n", (float)elap_inv/CLOCKS_PER_SEC);
	//printf (" old inversion time = %.4f s.\n", (float)elap_inv2/CLOCKS_PER_SEC);
	if (gfail) printf ("---> %d degree checks failed <---\n", gfail);
	if (afail) printf ("---> %d additions failed <---\n", afail);
	if (mfail) printf ("---> %d multiplications failed <---\n", mfail);
	if (dfail) printf ("---> %d distributions failed <---\n", dfail);
	if (sfail) printf ("---> %d squarings failed <---\n", sfail);
	if (ifail) printf ("---> %d inversions failed <---\n", ifail);
	if (rfail) printf ("---> %d square roots failed <---\n", rfail);
	if (tfail) printf ("---> %d traces failed <---\n", tfail);
	/*if (qfail) printf ("---> %d quadratic equations failed <---\n", qfail);*/
	//gfClear(x), gfClear(y), gfClear(z), gfClear(f), gfClear(g), gfClear(h); 
	return afail || mfail || dfail || dvfail || sfail || ifail || rfail || tfail || qfail;
}

#elif defined TRACE_MASK

main()
{
	gf_poly x;
	int tr, i;
	
	//gfOpen(x);
	gf2m_Set(x, 1);
	
	gfInit();
	printf("Polinomio: x^%d + x^%d + 1\n", GF_M, GF_T);
	for(i = 0; i < GF_M; ++i){
		/*tr = gf2m_SlowTrace(x);*/
		if((tr = gf2m_SlowTrace(x)))
			printf("Elemento: %d\tTraza: %d\n", i, tr);
		gf2m_Mul_x(x,x);
	}
	//gfClear(x);
	gfQuit();
}

#endif

