/*
 *  Cryptografic primitives on Elliptic Curve Group
 *	(Part of SKS cryptosystem)
 *
 * 
 * 	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>

#define _ECCRYPT_C 
#include "eccrypt.h"

static vl_number point_order;
static ec_point curve_point;
static _ecpoint curve_prevs[16];	/** 16 first values of j·Q **/
static vl_number uno;
static vl_number menosuno;
static int ECC_Init = 0;

#define eccMod(DST, SRC)		fp_mod(SRC, point_order, DST)
/*#define eccMod(DST, SRC)	{if (DST != SRC) vlCopy(DST, SRC);  \
							 while(fp_cmp_mag(DST, point_order) == FP_GT) \
							  fp_sub(DST, point_order, DST);} */
#define eccInvMod(DST,SRC)		fp_invmod(SRC, point_order, DST)
#define eccMulMod(DST,A, B)		fp_mulmod(A, B, point_order, DST)
/*#define eccAddMod(DST,A, B)		mp_addmod(A, B, point_order, DST)*/
#define eccAddMod(DST,A, B)		{fp_add(DST, A, B); \
								  while(fp_cmp_mag(DST, point_order) == FP_GT) \
							      fp_sub(DST, point_order, DST);}
#define eccPointSub(DST, DIM)	fp_sub(point_order, DIM, DST)
/*#define eccSetPoint(A)			ecCopy(A, curve_point)*/
#define vlRead(A,B)				fp_read_radix(A, B, 16);

/** Sets the firs 16 values of k·P **/
static void calculate_16 (ec_point prev, ec_point in)
{
	register int i;
	register _ecpoint *out = prev;

	//gfOpen(out->x); gfOpen(out->y); 
	ecZero(out++);
	//gfOpen(out->x); gfOpen(out->y); 
	ecCopy(out, in); ++out;
	for(i = 2; i < 16; i += 2, out += 2){
		//gfOpen(out->x); gfOpen(out->y);
		ecCopy(out, prev + i/2); ecDouble(out);
		//gfOpen((out+1)->x); gfOpen((out+1)->y);
		ecCopy(out + 1, out); ecAdd(out + 1, in);
		
	}

}

static void ecMultiplyAdd (ec_point p, vl_number k, vl_number j)
	/* sets p := k·p +j·Q
	Q: -> fixed point in the curve
	It uses projective coordinates 
	It uses first 16 previously calculated 
	values of k·p and j·Q */
{
	assert( (p != NULL) );
	assert ((j != NULL) || (k != NULL));
	//assert(ecExists(p);
	
	register int i;
	_ecpoint prev[16];	/** Table of first 16 values of k·P **/
	
	vlPoint kbuf;	/** byte buffer of input 'k'-number **/
	/*zeromem(kbuf, GF_SIZE);*/
	memset(kbuf, 0, GF_SIZE);
	
	vlPoint jbuf;	/** byte buffer of input 'j'-number **/
	/*zeromem(jbuf, GF_SIZE);*/
	memset(jbuf, 0, GF_SIZE);
	
	if(k != NULL){
		
		calculate_16(prev, p);
		vlPack (k, kbuf);
	}
	if(j != NULL) vlPack(j, jbuf);
	
	ec_projective r;
	unsigned char c, h;
	
	//ecProjOpen(r);
	gfZero(r->x); gfZero(r->y);
	gfSet(r->z, 1);
	
	
	for(i = 0; i < GF_SIZE; ++i){
		ecProjDouble(r);ecProjDouble(r);
		ecProjDouble(r);ecProjDouble(r);
		if( (c = kbuf[i]) ) ecProjAdd(r, prev + ((c >> 4) & 0x0f));
		if( (h = jbuf[i]) ) ecProjAdd(r, curve_prevs + ((h >> 4) & 0x0f));
		
		
		ecProjDouble(r);ecProjDouble(r);
		ecProjDouble(r);ecProjDouble(r);
		if((c)) ecProjAdd(r, prev + (c & 0x0f));
		if((h)) ecProjAdd(r, curve_prevs + (h & 0x0f));
	}
	ecProj2Affine(p, r);
	ecProjClear(r);
	
	if(k != NULL){
		for(i = 0; i < 16; ++i){
			gfClear((prev+i)->x);
			gfClear((prev+i)->y);
		}
	}
	
} /* ecMultiplyAdd */

ERROR eccInit()
{
	if(ECC_Init) return 0;
	gfInit();
	
	//vlOpen(point_order);
	//ecOpen(curve_point);
	/*vlUnpack(point_order, point_order_raw);*/
	vlRead(point_order, point_order_raw);
	if(ecUnpack(curve_point, curve_point_raw)) return 1;
	
	/** Sets the firs 16 values of j·Q **/
	calculate_16(curve_prevs, curve_point);
	
	#if 0
	unsigned int primo;
	fp_prime_is_prime(point_order, 200, &primo);
	if(!primo)	return 2;
	#endif
	
	/** Sets one and minus one constants **/
	//vlOpen(uno); vlOpen(menosuno);
	vlSet(uno, 1);
	eccPointSub(menosuno, uno);
	
	ECC_Init = 1;
	return 0;
}

void eccQuit()
{
	if (!ECC_Init) return;
	vlClear(point_order);
	ecClear(curve_point);
	vlClear(uno);
	vlClear(menosuno);
	
	int i;
	for(i = 0; i < 16; ++i){
		gfClear((curve_prevs+i)->x);
		gfClear((curve_prevs+i)->y);
	}
	
	gfQuit();
	ECC_Init = 0;
}

ERROR eccCheck(/* const */ PubKey a)
	/** Checks if input pubkey is OK
	**/
{
	ec_point dummy;
	ERROR err;
	
	if (!ECC_Init) eccInit();
	//ecOpen(dummy);
	if((err = ecUnpack(dummy, a))) return err;
/*	ecDouble(dummy);
	ecPack(dummy, doub);*/
	
	ecClear(dummy);
	return 0;
}

ERROR eccMakePublicKey (PubKey pub_raw, PrivKey priv_raw)
	/** sets P = d·Q where:
		P: public key
		d: priv. key
		Q: fixed curve point
	**/
{
	ec_point pub;
	vl_number priv;
	
	assert((pub_raw != NULL) && (priv_raw != NULL));
	if (!ECC_Init) eccInit();
	
	//vlOpen(priv); 
	vlUnpack(priv, priv_raw);
	eccMod(priv, priv);
	/** Values 0, 1 or (p-1) should be avoided **/
	if(	vl_iszero(priv) || vlEqual(priv, uno) || 
	  vlEqual(priv, menosuno)){
		
		vlClear(priv);
		return -1;
	}
	
	//ecOpen(pub);
	/*eccSetPoint(pub);
	ecMultiply(pub, priv);*/
	ecMultiplyAdd(pub, NULL, priv);
	ecPack(pub, pub_raw);
	
	vlClear(priv);
	ecClear(pub);
	return 0;
}

ERROR eccEncode (PubKey *pubs_raw, ecPoint *coded_raw, vlPoint sec_raw, int N)
	/** Different coded keys (M_i) are obtained by:
			M_i = k · P_i
		where 'k' is the secret multiplier and P_i, the pub. keys
		The secret code is
			K = k · Q
		where 'Q' is the fixed point in curve
	**/
{
	ec_point Q;
	vl_number sec;
	int i, err = 0;
	
	/*assert( (pubs_raw != NULL) && (coded_raw != NULL) && (sec_raw != NULL) );*/
	if (!ECC_Init) eccInit();
	//vlOpen(sec);
	vlUnpack(sec, sec_raw);
	eccMod(sec, sec);
	
	//ecOpen(Q);
	for(i = 0; i < N; ++i){
		if((err = ecUnpack(Q, pubs_raw[i]))) goto eccEncode_ERROR;
		ecMultiplyAdd(Q, sec, NULL);
		ecPack(Q, coded_raw[i]);
	}
	/*eccSetPoint(Q);
	ecMultiply(Q, sec);*/
	ecMultiplyAdd(Q, NULL, sec);
	ecPack(Q, sec_raw);

eccEncode_ERROR:	
	vlClear(sec); ecClear(Q);
	return err;
}

ERROR eccDecode (PrivKey priv_raw, ecPoint coded_raw, vlPoint sec_raw)
	/** Secret key 'K' is recovered by:
			K = (d^-1)·M
		where 'y' is the private key corresponding to recipient pub. key
			P = d·Q
		where Q is the fixed point in curve. 
		Proof:
			M  =  k·P  =  k·(d·Q)  =  d·(k·Q)  =  d·K
			(d^-1)·M  =  (d^-1)·d·K  =  K
	**/

{
	vl_number priv;
	ec_point M;
	
	if (!ECC_Init) eccInit();
	//ecOpen(M); 
	if(ecUnpack(M, coded_raw)){
		ecClear(M);
		return 1;
	}
	
	//vlOpen(priv); 
	vlUnpack(priv, priv_raw);
	
	eccInvMod(priv, priv);
	ecMultiplyAdd(M, priv, NULL);
	ecPack(M, sec_raw);
	
	vlClear(priv); ecClear(M);
	return 0;
}
	
ERROR eccSign(PrivKey priv_raw, vlPoint k_raw, vlPoint hash_raw, sgPair *sig)
	/** ECDSA signature (r, s):
			r <- (k·Q)x	
			s = (k)·((h + d.r)^-1)
			*** i.e. 'r' holds the x-coordinate of (k·Q) and considers it a number in Z(p)
		where:
			k: random multiplier
			Q: fixed curve point
			d: priv. key
			h: hash / MAC
	**/
{
	vl_number k, priv;
	_gf_poly *hsh, *r, *s;
	ec_point Q;
	
	if (!ECC_Init) eccInit();
	//vlOpen(k); ecOpen(Q); 
	vlUnpack(k, k_raw);
	
	
	eccMod(k, k);
	/*eccSetPoint(Q);
	ecMultiply(Q, k);*/
	ecMultiplyAdd(Q, NULL, k);
	r = (Q->x); 			/* r = (k·Q)x */
	eccMod(r, r);
	if(vl_iszero(r)) return -1;
	
	//vlOpen(priv); 
	vlUnpack(priv, priv_raw);	
	eccMod(priv, priv);
	eccMulMod(priv, r, priv);  /* d·r */
	s = priv;			
	
	hsh = (Q->y); vlUnpack(hsh, hash_raw);
	eccAddMod(s, hsh, s);		/* h + d·r */
	if(vl_iszero(s)) return -1;
	
	eccInvMod(s, s);			/* (h + d·r)^-1 */
	eccMulMod(s, k, s);			/* s = k·(h + d·r)^-1 */
	
	vlPack(s, sig->s);
	vlPack(r, sig->r);
	
	vlClear(k); vlClear(priv);
	ecClear(Q);
	return 0;
}

int eccVerify(PubKey pub_raw, vlPoint hash_raw, sgPair *sig)
	/** Now is performed the calculus:
			[(h·s)·Q + (r·s)·P]x
		where 'P' is the public key, P = d·Q
		and this must be equal to 'r'.
		Proof:
			(h·s)·Q + (r·s)·P = s·(h·Q + r·P) = s·(h·Q + r·d·Q) = s·(h + r·d)·Q
			by definition: s = 	k·(h + d·r)^-1, so
			(h·s)·Q + (r·s)·P = k·Q  ==>  [(h·s)·Q + (r·s)·P]x = [k·Q]x
			but 'r' is defined as: r = [k·Q]x
			QED
	**/
{
	ec_point pub;
	vl_number hsh, r, s;
	int ver;
	
	if (!ECC_Init) eccInit();
	/*ecOpen(Q); eccSetPoint(Q);*/
	//vlOpen(hsh); vlOpen(s);
	vlUnpack(hsh, hash_raw);
	vlUnpack(s, sig->s);
	
	eccMulMod(hsh, s, hsh);		/* h·s */
	/*ecMultiply(Q, hsh);	*/		/* (h·s)·Q */
	
	/*r = hsh;*/ 
	//vlOpen(r);
	vlUnpack(r, sig->r);
	eccMulMod(s, r, s); 		/* s <- r·s */
	
	//ecOpen(pub); 
	if(ecUnpack(pub, pub_raw)) { ver = 0; goto eccVerify_ERROR;}
	
	/*ecMultiply(pub, s);*/			/* (r·s)·P */
	/*ecAdd(Q, pub);	*/			/* (h·s)·Q + (r·s)·P */
	ecMultiplyAdd(pub, s, hsh);		/* (h·s)·Q + (r·s)·P */
	
	/*** Compare: [(h·s)·Q + (r·s)·P] == r ?? ***/
	eccMod(s, /*Q->x*/ pub->x);		/** Now s <- [(h·s)·Q + (r·s)·P]x **/
	ver = vlEqual(s, r);
	
eccVerify_ERROR:
	vlClear(hsh); vlClear(s); vlClear(r);
	ecClear(pub); /*ecClear(Q);*/
	
	return ver;
}

#ifdef MAIN

#include <time.h>

eccRandom(vlPoint r)
{
	int i, size = sizeof(unsigned long int);
	for(i = 0; i < GF_SIZE; i += size){
		*((unsigned long int *)&(r[i])) = rand();
	}
}

eccPrint(char *tag, vlPoint p)
{
	vl_number q;
	
	//vlOpen(q);
	vlUnpack(q, p);
	gfPrint(stderr, tag, q);
	vlClear(q);
}

main(int argc, char ** argv)
{
	int i, yb, crypt_fail = 0, sign_fail = 0,
		test_count;
	clock_t elapsed_crypt = 0L, elapsed_sign = 0L, elapsed_key = 0L;
	PubKey 		pub;
	PrivKey 	priv;
	vlPoint 	sec1, sec2, msg;
	sgPair		sig;
	ecPoint	cod;
	
	if(argc > 1){
		test_count = atoi(argv[1]);
	}
	else test_count = 1;
	
	eccInit();
	
	srand ((unsigned)(time(NULL) % 65521U));
	for(i = 0; i < test_count; ++i){
		
		/** Make key **/
		eccRandom(priv);
		elapsed_key -= clock();
		eccMakePublicKey(pub, priv);
		elapsed_key += clock();
		
		/** crypt/decrypt **/
		eccRandom(sec1);
		elapsed_crypt -= clock();
		eccEncode(&pub, &cod, sec1, 1);
		eccDecode(priv, cod, sec2);
		elapsed_crypt += clock();
		
		if(memcmp(sec1, sec2, GF_SIZE)){
			crypt_fail++;
			#ifdef DEBUG
			eccPrint("sec1", sec1);
			eccPrint("sec2", sec2);
			#endif
		}
		
		/** sign/verify **/
		eccRandom(msg);
		int ver;
		do{
			eccRandom(sec1);
			elapsed_sign -= clock();
			ver = eccSign(priv, sec1, msg, &sig);
			elapsed_sign += clock();
		} while(ver);
		
		elapsed_sign -= clock();
		ver = eccVerify(pub, msg, &sig);
		elapsed_sign += clock();
		if(!ver) sign_fail++;
		
	}
	
	printf ("Done.\nEncrypt-Decrypt time: %.3f s/cicle.\n",
		(float)elapsed_crypt/CLOCKS_PER_SEC/(test_count));
	printf ("Sign/Verify time: %.3f s/cicle.\n",
		(float)elapsed_sign/CLOCKS_PER_SEC/(test_count));
	printf ("Key generation time: %.3f s/key.\n",
		(float)elapsed_key/CLOCKS_PER_SEC/(test_count));
	if (crypt_fail) printf ("---> %d fails in encryption/decryption <---\n", crypt_fail);
	if (sign_fail) printf ("---> %d fails in signing /verifying <---\n", sign_fail);
	
	
	eccQuit();
	return crypt_fail | sign_fail;
}

#endif
