//
// Copyright (c) 2014 ANSCENTER. All rights reserved.
//

// elliptic curve implementation
#include "precomp.h"
#include "bigint.h"
#include "ec2m.h"
#include "prime.h"
#include <stdlib.h>
#include <time.h>

#define assert_on        0      // Consistency checking (for debugging)
#define use_field_op     0      // Use operators

#if assert_on
#include "prime.hpp"
static void assert0(int x){ if (x==0) while (1/x); }
#define assert(x) assert0(x)
#else
//#define assert(x)
#endif

#if 0
void biPrint2( const bigint & data )
{
	int n;
	unsigned * u;

	n = ( data.bits() + 31 ) / 32;

	u = new unsigned[ n ];

	data.store( u, n );

	printf( "%d ", data.bits() );

	for ( int i = n - 1; i >= 0; i-- )
	{
		printf( "%08X ", u[i] );
	}

	printf("\n");

	delete [] u;
}
#endif

// polynomial operations

int is_prime( unsigned p )
{
	for ( unsigned d = 2; d * d <= p; d += 1 )
		if ( p % d == 0 ) 
			return 0;

	return 1;
}

// Routines to perform operations on polynomials
// over F2 represented as bit strings (bigint)

bigint poly_div( const bigint & x, const bigint & y )
{
	unsigned m = y.bits() - 1;
	bigint d = 0;
	bigint rem = x;

	while ( 1 )
	{
		unsigned rb = rem.bits();

		if ( rb <= m ) 
			break;

		rem ^= y << ( rb - m - 1 );
		d ^= 1 << ( rb - m - 1 );
	}

	return d;
}

bigint poly_rem( const bigint & x, const bigint & y )
{
	unsigned m = y.bits() - 1;
	bigint rem = x;
	
	while ( 1 )
	{
		unsigned rb = rem.bits();
		
		if ( rb <= m ) 
			break;

		rem ^= y << ( rb - m - 1 );
	}

	return rem;
}

bigint poly_mul( const bigint & x, const bigint & y )
{
	bigint result = 0;
	unsigned yb = y.bits();

	for ( unsigned i = 0; i < yb; i += 1 )
	{
		if ( y.bit( i ) )
			result ^= x << i;
	}

	return result;
}

bigint poly_gcd( const bigint &x, const bigint &y ) // field is F2
{
	bigint a = x;
	bigint b = y;

	while ( b != 0 )
	{
		bigint c = poly_rem( a, b );
		a = b;
		b = c;
	}

	return a;
}

int poly_irreducible( const bigint & x )
{
	unsigned d = x.bits() - 1;
	bigint u = 2;

	for ( unsigned i = 1; i < d; i += 1 )
	{
		u = poly_rem( poly_mul( u, u ), x );
		bigint g = poly_gcd( u^2, x );

		if ( g != 1 ) 
			return 0;
	}

	return 1;
}

bigint poly_power( bigint x, bigint k, bigint g )
{
	bigint result = 1;

	for ( unsigned i = 0; i < k.bits(); i += 1 )
	{
		if ( k.bit( i ) )
		{
			result = poly_rem( poly_mul( result, x ), g );
		}

		x = poly_rem( poly_mul( x, x ), g );
	}

	return result;
}

unsigned calc_root( unsigned L )
{
	// find irreducible poly g such that 2 is primitive
	bigint base = pow2( L );

	for ( bigint g = base + 1; g < 2 * base; g += 2 ) 
		if ( poly_irreducible( g ) )
		{
			bigint r = base - 1;
			bigint f = 2;

			while ( 1 )
			{
				if ( f != base - 1 && r % f == 0 )
				{
					if ( poly_power( 2, ( base - 1 ) / f, g ) == 1 ) 
						break;

					do {
						r = r / f; 
					} while ( r % f == 0 );
				}

				if ( f >= r ) 
					return to_unsigned( g - base );

				if ( f == 2 ) 
					f = 3; 
				else 
					f += 2;

				if ( f * f > r ) 
					f = r;
			}
		}

	return 0; // should not happen, at least for reasonable L
}

// small_field implementation

small_field::small_field( unsigned L0, unsigned root )
: L( L0 ),
BASE( 1u << L0 ),
BASE_M1( ( 1u << L0 ) - 1 ),
log( new lunit[ 1u << L0 ] ),
alog( new lunit[ 1u << L0 ] )
{
	assert( L0 <= sizeof(lunit) * 8 );

	root |= BASE;
	unsigned j = 1;

	for ( unsigned i = 0; i < BASE_M1; i += 1 )
	{
		log[ j ] = i;
		alog[ i ] = j;
		j *= 2; // 2 is primitive element
		if ( j & BASE )
			j ^= root;
	}

	log[ 0 ] = BASE_M1; // used by field::mul
}

small_field::~small_field()
{
	delete [] log;
	delete [] alog;
}

unsigned * small_field::curve_order()
{
	unsigned * result = new unsigned[ BASE ]; // result[0] not defined
	unsigned b, x, y, i;

	for ( b = 1; b < BASE; b += 1 )
		result[ b ] = 2; /* 0,0 and 0, sqrt(b) */

	for ( x = 1; x < BASE; x += 1 )
	{
		unsigned lx = log[ x ];
		unsigned x3 = alog[ ( 3 * lx ) % BASE_M1 ];
		result[ x3 ] += 2; // y = 0 and y = x

		for ( y = 1; y < x; y += 1 )
		{
			i = log[ y ] + log[ x^y ];

			if ( i >= BASE_M1 ) 
				i -= BASE_M1;

			b = x3 ^ alog[ i ];
			result[ b ] += 1;
		}

		for ( y = x + 1; y < BASE; y += 1 )
		{
			i = log[ y ] + log[ x^y ];

			if ( i >= BASE_M1 ) 
				i -= BASE_M1;

			b = x3 ^ alog[ i ];
			result[ b ] += 1;
		}
	}

	return result;
}

// field implementation

void field::add( const poly a, const poly b, poly c )
{
	if ( a[ 0 ] < b[ 0 ] )
		add( b, a, c );
	else
	{
		unsigned i;

		for ( i = 0; i <= b[ 0 ]; i += 1 )
			c[ i ] = a[ i ] ^ b[ i ];

		for ( ; i <= a[ 0 ]; i += 1 )
			c[i] = a[i];

		c[ 0 ] = a[ 0 ];
	}

	while ( c[ 0 ] && c[ c[ 0 ] ] == 0 )
		c[ 0 ] -= 1;
}

void field::copy ( const poly a, poly b )
{
	for ( unsigned i = 0; i <= a[ 0 ]; i += 1 )
		b[ i ] = a[ i ];
}

int field::equal( const poly a, const poly b )
{
	unsigned i = 0;

	while ( 1 )
	{
		if ( a[ i ] != b[ i ] ) 
			return 0;

		if ( i == a[ 0 ] )
			return 1;

		i += 1;
	}
}

void field::set_random( poly a )
{
	a[0] = K;

	for ( unsigned i = 1; i <= K; i += 1 )
		a[ i ] = rand( BASE );

	while ( a[ 0 ] && a[ a[ 0 ] ] == 0 )
		a[ 0 ] -= 1;
}

void field::reduce( poly a )
{
	for ( unsigned i = a[ 0 ]; i > K; i -= 1 )
	{
		a[ i - K ] = a[ i - K ] ^ a[ i ];
		a[ i + T - K ] = a[ i + T - K ] ^ a[ i ];
		a[ i ] = 0;
	}

	if ( a[0] > K )
	{
		a[0] = K;

		while ( a[ 0 ] && a[ a[ 0 ] ] == 0 ) 
			a[ 0 ] -= 1;
	}
}

// field_element implementation

field_element & field_element:: operator = ( const field_element & x )
{
	f = x.f;

	field::copy( x.v, v );

	return *this;
}

field_element::field_element()
{
	f = 0;
	v[ 0 ] = 0;
}

field_element::field_element( field * F )
{
	f = F;
	v[ 0 ] = 0;
}

field_element::field_element( const field_element & x )
{
	f = x.f;
	field::copy( x.v, v );
}

int field_element::operator == ( const field_element & x ) const
{
	return field::equal( v, x.v );
}

int field_element::operator == ( unsigned x ) const
{
	return ( v[ 0 ] == 0 && x == 0 ) || ( v[ 1 ] == 1 && x == v[ 1 ] );
}

field_element field_element::operator + ( const field_element & x ) const
{
	field_element result( f );

	field::add( v, x.v, result.v );

	return result;
}

field_element field_element::operator * ( const field_element & x ) const
{
	field_element result( f );

	f->mul( v, x.v, result.v );
	f->reduce( result.v );

	return result;
}

field_element curve::sq( const field_element & x )
{
	field_element result( x.f );

	x.f->square( x.v, result.v );
	x.f->reduce( result.v );

	return result;
}

field_element field_element::operator / ( const field_element & x ) const
{
	field_element result( f );
	field::poly   tmp;

	f->inverse( x.v, tmp );
	f->mul( v, tmp, result.v );
	f->reduce( result.v );

	return result;
}

field_element sqrt( const field_element x )
{
	field_element result( x.f );

	x.f->sqrt( x.v, result.v );

	return result;
}

// field implementation

int field::slow_trace( const poly a )
{
	field::poly t1;

	copy( a, t1 );

	for ( unsigned i = 1; i < M; i += 1 )
	{
		field::poly t2;
		square( t1, t2 );
		reduce( t2 );
		add( t2, a, t1 );
	}

	return t1[ 0 ] != 0;
}

int field::trace( const poly a )
{
	unsigned w = 0;
	unsigned min = a[ 0 ]; 

	if ( min > tm[ 0 ] ) 
		min = tm[ 0 ];

	for ( unsigned i = 1; i <= min; i += 1 )
		w ^= a[ i ] & tm[ i ];

	// compute parity of w
	unsigned result = 0;

	while ( w )
	{
		result ^= w & 1;
		w >>= 1;
	}

	assert( result == (unsigned)slow_trace( a ) );
	
	return result;
}

void field::quad_solve( const poly a, poly b )
{
	if ( M % 2 == 1 ) // M is odd - compute half-trace
	{
		copy( a, b );

		for ( unsigned i = 0; i < M / 2; i += 1 )
		{
			field::poly t1, t2;
			square( b, t1 ); reduce( t1 );
			square( t1, t2 ); reduce( t2 );
			add( t2, a, b );
		}
	}
	else
	{
		field::poly d;
		b[ 0 ] = 0;
		copy( a, d );

		for ( unsigned i = 1; i < M; i += 1 )
		{
			field::poly t1, t2;
			square( d, t1 ); reduce( t1 );
			mul( nzt, t1, t2 ); // N.B. make nzt the first parameter, since it is mostly zero
			square( b, t1 );
			add( t1, t2, b ); reduce( b );
			square( d, t1 ); reduce( t1 );
			add( t1, a, d );
		}
	}
}

unsigned field::rand( unsigned base )
{
	// A very bad prng - but should be good enough for initialisation
	// You can use whatever prng you like here
	// but it needs to be standard so that
	// the same point C.P is always generated
	// for a given root,CB

	prng += 1;

	return prng % base;
}

field::field( full_curve_parameter & a )
:small_field( a.L, a.root ), M( a.L * a.K ), K( a.K ), T( a.T )
{
	assert( K <= MAXK );

	prng = 0;

	if ( a.tm == 0 )
	{
		// Calculate trace mask
		poly x;
		for ( unsigned i = 1; i <= K; i += 1 )
		{
			x[ 0 ] = i;
			unsigned m = 1;
			unsigned w = 0;

			for ( unsigned j = 0; j < L; j += 1 )
			{
				x[ i ] = m;

				if ( slow_trace( x ) )
					w ^= m;

				m *= 2;
			}

			 x[ i ] = 0;
			tm[ i ] = w;

			if ( w )
				tm[ 0 ] = i;
		}

		a.tm = pack( tm );
	}
	else
		unpack( a.tm, tm );

	if ( M % 2 == 0 )
	{
		nzt[ 0 ] = 1;
		nzt[ 1 ] = 1;

		while ( ( nzt[ 1 ] & tm[ 1 ] ) == 0 )
			nzt[ 1 ] *= 2;

		assert( trace( nzt ) != 0 );
	}

#if assert_on // test basic field operations
	for ( unsigned i = 0; i < 10; i += 1 )
	{
		field_element a( this ), b( this ), c( this );
		
		set_random( a.v );
		set_random( b.v ); 
		set_random( c.v );
		
		assert( a + b == b + a );
		assert( a * b == b * a );
		assert( a * ( b + c ) == a * b + a * c );

		if ( b.v[ 0 ] )
			assert( a == b * ( a / b ) );
		
		assert( ::sqrt( a ) * ::sqrt( a ) == a );
	}
#endif
}

void field::mul( const poly a, const poly b, poly c )
{
	unsigned i, j;

	assert( a[0] <= K );
	assert( b[0] <= K );

	if ( a[ 0 ] == 0 || b[ 0 ] == 0 )
	{
		c[ 0 ] = 0;
		return;
	}

	c[ 0 ] = a[ 0 ] + b[ 0 ] - 1;

	for ( i = 1; i <= c[ 0 ]; i += 1 )
		c[ i ] = 0;

	// pre-computation of log[b[j]] (to reduce table lookups)
	unsigned lb[ MAXK + 1 ];
	unsigned b0 = b[ 0 ];

	for ( j = 1; j <= b0; j += 1 )
		lb[ j ] = log[ b[ j ] ];

	for ( i = 1; i <= a[ 0 ]; i += 1 )
	{
		unsigned lai = log[ a[ i ] ];
		if ( lai != BASE_M1 )
		{
			for ( j = 1; j <= b0; j += 1 )
			{
				unsigned lbj = lb[ j ];
				if ( lbj != BASE_M1 )
				{
					unsigned x = lai + lbj;
					
					if ( x >= BASE_M1 ) 
						x -= BASE_M1;

					c[ i + j - 1 ] ^= alog[ x ];
				}
			}
		}
	}
}

void field::square( const poly a, poly c )
{
	unsigned i;

	if ( a[ 0 ] == 0 )
	{
		c[ 0 ] = 0;
		return;
	}

	c[ 0 ] = 2 * a[ 0 ] - 1;

	for ( i = 1; ; i += 1 )
	{
		unsigned x = 0;
		if ( a[ i ] )
		{
			x = 2 * log[ a[ i ] ];

			if ( x >= BASE_M1 ) 
				x -= BASE_M1;

			x = alog[ x ];
		}

		c[ 2 * i - 1 ] = x;

		if ( i == a[ 0 ] )
			break;

		c[ 2 * i ] = 0;
	}
}

void field::addmul( poly a, unsigned alpha, unsigned j, const poly b )
{
	unsigned la = log[ alpha ];

	for ( unsigned  i = 1; i <= b[ 0 ]; i += 1 )
	{
		while ( a[ 0 ] < j + i )
		{
			a[ 0 ] += 1;
			a[ a[ 0 ] ] = 0; 
		}

		unsigned x = b[i];

		if ( x )
		{
			x = log[ x ] + la;

			if ( x >= BASE_M1 ) 
				x -= BASE_M1;

			a[ i + j ] ^= alog[ x ];
		}
	}

	while ( a[ 0 ] && a[ a[ 0 ] ] == 0 )
		a[ 0 ] -= 1;
}

void field::div( poly a, unsigned b )
{
	unsigned lb = log[b];

	for ( unsigned i = 1; i <= a[ 0 ]; i += 1 )
	{
		unsigned ix = a[ i ];
		if ( ix )
		{
			ix = log[ ix ] + BASE_M1 - lb;

			if ( ix >= BASE - 1 ) 
				ix -= BASE_M1;
			
			a[ i ] = alog[ ix ];
		}
	}
}

void field::inverse( const poly a, poly b )
{
	field::poly c, f, g;

	b[ 0 ] = 1; 
	b[ 1 ] = 1;
	c[ 0 ] = 0;
	g[ 0 ] = K + 1;

	copy( a, f );

	for ( unsigned i = 2; i <= K; i += 1 )
		g[ i ] = 0;

	g[ 1 ] = 1; 
	g[ T + 1 ] = 1; 
	g[ K + 1 ] = 1;

	while ( 1 )
	{
		if ( f[ 0 ] == 1 )
		{
			div( b, f[1] );
			return;
		}
		if ( f[ 0 ] < g[ 0 ] ) // basically same code with b,c,f,g swapped
		{
			while ( 1 )
			{
				unsigned j = g[ 0 ] - f[ 0 ];
				unsigned ix = log[ g[ g[ 0 ] ] ] - log[ f[ f[ 0 ] ] ] + BASE_M1;

				if ( ix >= BASE_M1 ) 
					ix -= BASE_M1;

				unsigned alpha = alog[ ix ];
				addmul( g, alpha, j, f );
				addmul( c, alpha, j, b );

				if ( g[ 0 ] == 1 )
				{
					div( c, g[ 1 ] );
					copy( c, b );
					return;
				}

				if ( g[ 0 ] < f[ 0 ] ) 
					break;
			}
		}
	
		unsigned j = f[ 0 ] - g[ 0 ];
		unsigned ix = log[ f[ f[ 0 ] ] ] - log[ g[ g[ 0 ] ] ] + BASE_M1;

		if ( ix >= BASE_M1 ) 
			ix -= BASE_M1;

		unsigned alpha = alog[ ix ];
		addmul( f, alpha, j, g );
		addmul( b, alpha, j, c );
	}
}

void field::sqrt( const poly a, poly b ) // b = a^( 2^(M-1) );
{
	unsigned i = M-1;

	copy( a, b );

	while ( i-- )
	{
		field::poly t1;
		square( b, t1 );
		reduce( t1 );
		copy( t1, b );
	}
}

bigint field::pack( const poly a )
{
	bigint result;
	unsigned i = a[ 0 ];

	while ( i )
	{
		result = ( result << L ) + a[ i ];
		i -= 1;
	}

	return result;
}

void field::unpack( const bigint & X, poly a )
{
	bigint x = X;
	unsigned n = 0;

	while ( x != 0 )
	{
		n += 1;
		a[ n ] = to_unsigned( x & BASE_M1 );
		x >>= L;
	}

	a[ 0 ] = n;
}

// point implementation

point::point()
{
	c = 0;
}

point::point( curve * C ) : x(C), y(C)
{
	c = C;
}

point::point( const point & P )
{
	c = P.c;
	x = P.x;
	y = P.y;
}

point operator * ( const bigint & k, const point & P )
{
	point result( P.c );
	P.c->mul( P, k, result );

	return result;
}

point point::operator + ( const point &P ) const
{
	point result( P.c );
	c->add( *this, P, result );

	return result;
}

point point::operator - ( const point & P ) const
{
	point result( P.c );
	c->sub( *this, P, result );

	return result;
}

point & point::operator = ( const point & P )
{
	c = P.c;
	x = P.x;
	y = P.y;

	return *this;
}

// curve implementation

curve::curve( full_curve_parameter & a ) : field( a )
{
	b.f = this;
	b.v[0] = 1;
	b.v[1] = a.b;

	unsigned SQRT_BASE = 1u << ( ( a.L + 1 ) / 2 ); // upper bound
	unsigned so_min = BASE - 2 * SQRT_BASE;
	unsigned so = so_min + 2 * a.nso;
	bigint sf = so * a.ntf;

	p = a.p0;

	if ( p == 0 )
	{
		bigint Z = pow2( L );
		p = pow2( M ) + 1 - small_lucas( Z - ( so-1 ), Z, K );
		p = p / sf;
		a.p0 = p;
	}

	assert( is_probable_prime( p ) && MOV( M / 8, pow2( M ), p ) );

	if ( a.P0 == 0 )
	{
		prng = 0; // to make sure P is reproducible

		do {
			PP = sf * random_point();
		} while ( PP.x.v[ 0 ] + PP.y.v[ 0 ] == 0 );

		a.P0 = pack( PP );
	}
	else
		PP = unpack( a.P0 );

#if assert_on
	point t2 = p * PP;
	assert( t2.x.v[ 0 ] + t2.y.v[ 0 ] == 0 ); // fingers crossed!
#endif
};

unsigned curve::ybit( const field_element & x )
{
	if ( x.v[ 0 ] == 0 )
		return 0;

	return x.v[ 1 ] & 1;
}

bigint curve::to_vlong( const point & P )
{
	return P.c->field::pack( P.x.v );
}

bigint curve::pack( const point & P )
{
	bigint result = 0;

	if ( P.x.v[0] )
	{
		result = to_vlong( P );
		result = result * 2 + ybit( P.y / P.x );
	}
	else 
	if ( P.y.v[0] )
		result = 1;
	else
		result = 0;

	return result;
}

point curve::unpack( const bigint & X )
{
	bigint x = X;
	unsigned yb = to_unsigned( x & 1 ); 
	point R( this );

	x >>= 1;
	field::unpack( x, R.x.v );

	if ( R.x.v[0] || yb )
		calc_y( R, yb ); // will return 1 provided X is valid
	else
		R.y = R.x; // zero

	return R;
}

int curve::MOV( unsigned B, const bigint & q, const bigint & r )
{
	bigint x = 1;

	for ( unsigned i = 1; i < B; i += 1 )
	{
		x = ( x * q ) % r;
		if ( x == 1 )
			return 0; // what a pity ...
	}

	return 1;
}

bigint curve::small_lucas( bigint P, bigint Z, unsigned ik )
{
	bigint V0 = 2;
	bigint V1 = P;

	while ( ik > 1 )
	{
		bigint V2 = P * V1 - Z * V0;
		V0 = V1;
		V1 = V2;
		ik -= 1;
	}

	return V1;
}

#if use_field_op

void curve::sub( const point & P, const point & Q, point & R )
{
	point t1 = Q;

	t1.y = t1.y + t1.x;
	add( P, t1, R );
}

void curve::add( const point & P, const point & Q, point & R )
{
	if ( P.x == 0 && P.y == 0 )
		R = Q;
	else 
	if ( Q.x == 0 && Q.y == 0 )
		R = P;
	else
	{
		if ( P.x == Q.x )
		{
			if ( P.y == Q.y ) // points are equal
			{
				field_element LD = P.x + P.y / P.x;
				R.x = sq( LD ) + LD /*+ a*/;
				R.y = sq( P.x ) + LD * R.x + R.x;
			}
			else // must be inverse - result is zero
			{
				R.x.v[ 0 ] = 0;
				R.y.v[ 0 ] = 0;
			}
		}
		else // P,Q are distinct, non-zero, and P != -Q
		{
			field_element LD = ( P.y + Q.y ) / ( P.x + Q.x );
			R.x = sq( LD ) + LD + P.x + Q.x /*+ a*/;
			R.y = LD * ( P.x + R.x ) + R.x + P.y;
		}
	}
}

#else

// optimised version - about 5% faster ?
// could still do better

void curve::sub( const point & P, const point & Q, point & R )
{
	point t1;

	t1.c = this;
	copy( Q.x.v, t1.x.v );
	field::add( Q.x.v, Q.y.v, t1.y.v );
	add( P, t1, R );
}

void curve::add( const point & P, const point & Q, point & R )
{
	if ( P.x.v[ 0 ] + P.y.v[ 0 ] == 0 )
		R = Q;
	else 
	if ( Q.x.v[ 0 ] + Q.y.v[ 0 ] == 0 )
		R = P;
	else
	{
		field::poly LD, t1, t2, t3;
		field::add( P.x.v, Q.x.v, t1 );

		if ( t1[ 0 ] == 0 )
		{
			if ( equal( P.y.v, Q.y.v ) ) // points are equal
			{
				// field_element LD = P.x + P.y/P.x;
				inverse( P.x.v, t1 );
				field::mul( t1, P.y.v, t2 ); reduce( t2 );
				field::add( t2, P.x.v, LD );
				// R.x = sq(LD) + LD /*+ a*/;
				square( LD, t1 ); reduce( t1 );
				field::add( t1, LD, R.x.v );
				// R.y = sq(P.x) + LD*R.x + R.x;
				square( P.x.v, t1 );
				field::mul( LD, R.x.v, t2 );
				field::add( t1, t2, t3 );
				reduce( t3 );
				field::add( t3, R.x.v, R.y.v );
			}
			else // must be inverse - result is zero
			{
				R.x.v[0] = 0;
				R.y.v[0] = 0;
			}
		}
		else // P,Q are distinct, non-zero, and P != -Q
		{
			//field_element LD = ( P.y + Q.y ) / ( P.x + Q.x );
			inverse( t1, t2 );
			field::add( P.y.v, Q.y.v, t1 );
			field::mul( t1, t2, LD ); reduce(LD);
			//R.x = sq(LD) + LD + P.x + Q.x /*+ a*/;
			square( LD, t1 ); reduce( t1 );
			field::add( t1, LD, t2 );
			field::add( t2, P.x.v, t1 );
			field::add( t1, Q.x.v, R.x.v );
			//R.y = LD*(P.x+R.x) + R.x + P.y;
			field::add( P.x.v, R.x.v, t1 );
			field::mul( LD, t1, t2 ); reduce( t2 );
			field::add( t2, R.x.v, t1 );
			field::add( t1, P.y.v, R.y.v );
		}
	}
}

#endif

point curve::random_point()
{
	point R( this );

	do {
		field::set_random( R.x.v );
	} while ( calc_y( R ) == 0 );

	return R;
}

int curve::calc_y( point & R, unsigned yb )
{
	if ( R.x.v[ 0 ] == 0 )
		sqrt( b.v, R.y.v );
	else
	{
		field_element alpha = sq( R.x ) * ( R.x /*+ a*/ ) + b;
		R.y.v[ 0 ] = 0;

		if ( alpha.v[ 0 ] != 0 )
		{
			field_element t1( this );

			field_element beta = alpha / sq( R.x );

			if ( trace( beta.v ) != 0 )
				return 0;

			quad_solve( beta.v, t1.v );

			assert( t1 * t1 + t1 == beta );
			
			if ( ybit( t1 ) != yb )
				t1.v[ 1 ] ^= 1;

			R.y = R.x * t1;
		}
	}

	assert( sq( R.y ) + R.x * R.y == R.x * R.x * R.x + b );
	
	return 1;
}

void curve::mul( const point & P, const bigint & x, point & Q )
{
	bigint h = x*3;
	Q.x.v[0] = 0;
	Q.y.v[0] = 0;

	if (h.bits() == 0)
		return;

	point R = P;

	unsigned r = h.bits()-1; // so h.bit(r)==1

	unsigned i = 1;

	while ( 1 )
	{
		int hi = h.bit( i );
		int ai = x.bit( i );

		if ( hi == 1 && ai == 0 )
			Q = Q + R;
		
		if ( hi == 0 && ai == 1 )
			Q = Q - R;
		
		if ( i == r ) 
			break;

		i += 1;
		R = R + R;
	}
}

// full_curve_parameter implementation

full_curve_parameter::full_curve_parameter( const curve_parameter & bp )
{
	L = bp.L;
	K = bp.K;
	T = bp.T;
	root = bp.root;
	b = bp.b;
	nso = bp.nso;
	ntf = bp.ntf;
	p0 = 0;
	P0 = 0;
	tm = 0;
}

// curve_factory implementation

curve_factory::curve_factory( unsigned L ) 
: L( L )
{
	unsigned BASE = 1u << L;
	unsigned SQRT_BASE = 1u << ( ( L+1 ) / 2 ); // upper bound
	unsigned * ob;
	unsigned so;

	if ( L > MAXL )
		return;

	so_set = 0;

	{
		root = calc_root( L );

		if ( root == 0 ) 
			return;

		small_field f( L, root );
		so_min = BASE - 2 * SQRT_BASE;
		so_max = BASE + 2 * SQRT_BASE;
		ob = f.curve_order();
	}

	so_set = new unsigned[ so_max - so_min + 1 ];

	for ( so = so_min; so <= so_max; so += 1 ) 
		so_set[ so-so_min ] = 0;

	for ( unsigned b = 1; b < BASE; b += 1 )
	{
		so = ob[ b ];

		assert( so >= so_min );
		assert( so <= so_max );
		assert( ( so & 1 ) == 0 );

		if ( so_set[ so - so_min ] == 0 )
			so_set[ so - so_min ] = b;
	}

	delete [] ob;

	{
		prime_factory pf( 1000 );
		comp = 1;

		for ( unsigned i = 0; i < pf.np; i += 1 )
			comp = comp * pf.pl[ i ];
	}
}

curve_factory::~curve_factory()
{
	delete [] so_set;
}

int curve_factory::find( unsigned K, curve_parameter & result )
{
	unsigned T = 1;

	if ( !so_set || K < 3 || !is_prime( K ) || L % K == 0 )
		return 0;

	// calculate T
	while ( 1 )
	{
		if ( poly_irreducible( 1 + pow2( T ) + pow2( K ) ) )
			break;

		T += 1;

		if ( T > K / 2 )
			return 0;
	}

	bigint best_p = 1000;
	int found = 0;

	for ( unsigned so = so_min; so <= so_max; so += 1 )
	{
		unsigned M = L * K;
		unsigned b = so_set[ so - so_min ];
		if ( b )
		{
			bigint Z = pow2( L );
			bigint p = pow2( M ) + 1 - curve::small_lucas( Z - ( so - 1 ), Z, K );
			assert( p % so == 0 );
			p = p / so;
			bigint ntf = 1;
			bigint factor = comp;

			while ( 1 )
			{
				factor = gcd( p, factor );
				// printf("L=%u K=%u so=%u factor=%d\n",L,K,so,to_unsigned(factor));
				if ( factor == 1 )
				{
					if ( is_probable_prime( p ) && curve::MOV( 10, pow2( M ), p ) )
					{
						result.L = L;
						result.K = K;
						result.T = T;
						result.root = root;
						result.b = b;
						result.nso = ( so - so_min ) / 2;
						result.ntf = to_unsigned( ntf );
						found = 1;
						best_p = p;

						if ( result.ntf == 1 )
							return 1;
					}

					break;
				}
				else
				{
					ntf = ntf * factor;

					if ( ntf > 0xffff )
						break; // limit on non-trivial factors

					p = p / factor;

					if ( p <= best_p )
						break;
				}
			}
		}
	}

	return found;
}

// ECC ( Elliptic Curve Cryptography ) implementation

ECC_BASE::ECC_BASE( full_curve_parameter & a )
: curve( a ), bits( p.bits() )
{
#ifdef WIN32
	if (!CryptAcquireContext(&m_cryptProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
		m_cryptProv = NULL;
#else
	::srand((unsigned)time(NULL));
#endif
}

ECC_BASE::~ECC_BASE()
{
#ifdef WIN32
	CryptReleaseContext(m_cryptProv, 0L);
#endif
}

bigint ECC_BASE::create_private_key()
{
	bigint x;

	while ( x < p )
	{
		x = ( x << 15 ) + rand( 1u << 15 );
	}

	x = 2 + x % ( p - 3 );

	return x;
}

unsigned ECC_BASE::rand(unsigned base)
{
#ifdef WIN32
	unsigned rnd;
	CryptGenRandom(m_cryptProv, sizeof(base), (BYTE *)&rnd);
	return rnd % base;
#else
	return ::rand();
#endif
}

bigint ECC_BASE::create_public_key( const bigint & private_key )
{
	return pack( private_key * PP );
}

bigint ECC_BASE::encrypt( const bigint & public_key, bigint & message )
{
	bigint sr = create_private_key();

	message = pack( sr * PP );

	return to_vlong( sr * unpack( public_key ) );
}

bigint ECC_BASE::decrypt( const bigint & private_key, const bigint & message )
{
	point D = private_key * unpack( message );

	return to_vlong( D );
}

bigint_pair ECC_BASE::schnorr_sign( const bigint & data, const bigint & private_key, bigint (*hash)(const bigint &, const bigint &), unsigned hashbits )
{
	bigint_pair sig;
	bigint k;

	do
	{
		k = create_private_key();
		bigint z = to_vlong( k*PP );
		sig.r = (hash) ? hash( z + data, p) % (pow2(hashbits) - 1) : ( z + data ) % p;
	} while ( sig.r == 0 );

	sig.s = ( k + private_key * sig.r ) % p;

	return sig;
}

bool ECC_BASE::schnorr_verify( const bigint & e, const bigint_pair & sig, const bigint & public_key, bigint (*hash)(const bigint &, const bigint &), unsigned hashbits )
{
	bigint bz = to_vlong( sig.s * PP - sig.r * unpack( public_key ) );
	bigint data = (hash) ? hash( e + bz, p ) % (pow2(hashbits) - 1) : ( e + bz ) % p;

	return (data == sig.r) ? true : false; // avoid compiler warning
}

bigint_pair ECC_BASE::dsa_sign( const bigint & data, const bigint & private_key, bigint (*hash)(const bigint &, const bigint &))
{
	bigint_pair sig;
	bigint k;

	do
	{
		k = create_private_key();
		bigint z = to_vlong( k*PP );
		sig.r = z % p;
	} while ( sig.r == 0 );

	sig.s = ( modinv( k, p ) * ( hash( data, p ) + private_key * sig.r ) ) % p;

	return sig;
}

bool ECC_BASE::dsa_verify( const bigint & e, const bigint_pair & sig, const bigint & public_key, bigint (*hash)(const bigint &, const bigint &))
{
	bigint c = modinv( sig.s, p );	
	bigint u1 = ( hash( e, p ) * c ) % p;
	bigint u2 = ( sig.r * c ) % p;
	bigint z = to_vlong( u1 * PP + u2 * unpack( public_key ) ) % p;

	return (z == sig.r) ? true : false;
}
