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

#include "precomp.h"
#include "ecc.h"
#include "bigint.h"
#include "ec2m.h"
#include "sha1.h"
#include "except.h"
#include "base64.h"
#include "uniconv.h"

#ifndef _WIN32
#include <alloca.h>
#else
#include <malloc.h>
#endif

namespace ECC {

#if 0
void biPrint( 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

static const curve_parameter eccCurveTable[ 10 ] = { { 9, 7, 1, 17, 43, 48, 1},
	  										  { 12, 7, 1, 83, 2049, 10, 1 }, 
										      { 13, 7, 1, 27, 124, 52, 1},
											  { 15, 7, 1, 3, 977, 110, 1},
											  { 16, 7, 1, 45, 2902, 6, 1},
											  { 10, 11, 2, 9, 139, 34, 1},
											  { 12, 11, 2, 83, 2083, 66, 1},
											  { 13, 11, 2, 27, 773, 48, 1},
											  { 14, 11, 2, 43, 146, 40, 1},
											  { 10, 17, 3, 9, 48, 28, 1} };

static void UintArrayToByteArray(unsigned * srcArray, int bitCount, unsigned char * dstArray)
{
    int dstLength = ((int)bitCount + 7) >> 3;
	int srcLength = (dstLength + 3) >> 2;

    int i = 0;
    int j = 0;
    unsigned currentUint;

    if (dstLength > 3)
    {
        while (i < dstLength - 3)
        {
            currentUint = (j < srcLength) ? srcArray[j] : 0;

            dstArray[i + 3] = (unsigned char)(currentUint >> 24);
            dstArray[i + 2] = (unsigned char)((currentUint >> 16) & 0xFF);
            dstArray[i + 1] = (unsigned char)((currentUint >> 8) & 0xFF);
            dstArray[i] = (unsigned char)(currentUint & 0xFF);
            i += 4;
            j += 1;
        }
    }

    currentUint = (j < srcLength) ? srcArray[j] : 0;
    if (i < dstLength) dstArray[i] = (unsigned char)(currentUint & 0xFF); i++;
    if (i < dstLength) dstArray[i] = (unsigned char)((currentUint >> 8) & 0xFF); i++;
    if (i < dstLength) dstArray[i] = (unsigned char)((currentUint >> 16) & 0xFF);
}

static void ByteArrayToUintArray(unsigned char * srcArray, int srcLength, unsigned * dstArray)
{
    int roundedLen = (srcLength + sizeof(unsigned) - 1) / sizeof(unsigned);

    int i = 0;
    int j = 0;
    while (i < roundedLen - 1)
    {
        dstArray[i] = (unsigned)(((unsigned)srcArray[j + 3] << 24) | ((unsigned)srcArray[j + 2] << 16) | ((unsigned)srcArray[j + 1] << 8) | (unsigned)srcArray[j]);
        i += 1;
        j += 4;
    }

    dstArray[roundedLen - 1] = 0;

    if (j < srcLength) dstArray[roundedLen - 1] = (unsigned)(srcArray[j]); j++;
    if (j < srcLength) dstArray[roundedLen - 1] |= (unsigned)((unsigned)srcArray[j] << 8); j++;
    if (j < srcLength) dstArray[roundedLen - 1] |= (unsigned)((unsigned)srcArray[j] << 16); j++;
    if (j < srcLength) dstArray[roundedLen - 1] |= (unsigned)((unsigned)srcArray[j] << 24);
}

class KeyImpl {
friend class Key;
friend class SignerImpl;
friend class VerifierImpl;

friend void GenerateKeyPair(KEY_TYPE keyType, Key **privKey, Key **pubKey);

private:
	KeyImpl()
	{

	}

	KeyImpl(const void * keyBuf, int bufLen, KEY_TYPE keyType = ECC)
	{
		Load(keyBuf, bufLen, keyType);		
	}

	KeyImpl(const char * base64Key, KEY_TYPE keyType = ECC)
	{
		
		BASE64 base64;

		int len;
		
		auto buf = base64.decode(base64Key, strlen(base64Key), &len);

		if (buf.empty())
			throw new LicensingException(STATUS_INVALID_PARAM, "invalid base64 key");

		Load(buf.data(), len, keyType);
	}

	void Load(const void * keyBuf, int bufLen, KEY_TYPE keyType = ECC)
	{
		if (keyType == ECC)
		{
			unsigned char * keyBytes = (unsigned char *)keyBuf;

			if ((keyBytes[0] >> 5) != 0)
				throw new LicensingException(STATUS_INVALID_PARAM, "unsupported key version");

			if (keyBytes[1] + 2 != bufLen)
				throw new LicensingException(STATUS_INVALID_PARAM, "invalid key size");

			isPrivate = ((keyBytes[0] & 0x10) != 0);
			SetKeyType((KEY_TYPE)(keyBytes[0] & 0x0F));

			int roundedLen = sizeof(unsigned) * ((bufLen - 2 + sizeof(unsigned) - 1) / sizeof(unsigned));
			unsigned * buf = (unsigned *)calloc(roundedLen, 1);
			if (!buf) return;

			ByteArrayToUintArray(keyBytes + 2, bufLen - 2, buf);

			m_key.load(buf, (unsigned)(roundedLen / sizeof(unsigned)));

			free(buf);
		} else
		{
			SetKeyType(keyType);

			int roundedLen = sizeof(unsigned) * ((bufLen + sizeof(unsigned) - 1) / sizeof(unsigned));
			unsigned * buf = (unsigned *)calloc(roundedLen, 1);
			if (!buf) return;

			ByteArrayToUintArray((unsigned char *)keyBuf, bufLen, buf);

			m_key.load(buf, (unsigned)(roundedLen / sizeof(unsigned)));

			free(buf);
		}
	}

	bool Store(void * buf, int * bufLen)
	{
		unsigned char * keyBytes = (unsigned char *)buf;

		int numBits = m_key.bits();

		int numBytes = (numBits + 7) >> 3;

		if (*bufLen < 2 + numBytes)
			return false;

		keyBytes[0] &= 0x1F; // set first 3 bits to 0 - version 1
		if (isPrivate) keyBytes[0] |= 0x10; else keyBytes[0] &= 0xEF; // the 4th bit is 1 if it's a private key
		keyBytes[0] = (keyBytes[0] & 0xF0) | (((unsigned char)m_keyType) & 0x0F); // the next 4 bits from 1st byte are the key type
		keyBytes[1] = numBytes; // the second byte specifies the key size

		int numUints = (numBytes + sizeof(unsigned) - 1) / sizeof(unsigned);
		unsigned * temp = (unsigned int *)_alloca(numUints * sizeof(unsigned));
		m_key.store(temp, numUints);

		UintArrayToByteArray(temp, numBits, keyBytes + 2);

		*bufLen = numBytes + 2;

		return true;
	}

	void SetKeyType(KEY_TYPE keyType)
	{
		m_keyType = keyType;
		switch (keyType)
		{
		case ECC_54: m_keySize = 54;break;
		case ECC_73: m_keySize = 73;break;
		case ECC_79: m_keySize = 79;break;
		case ECC_91: m_keySize = 91;break;
		case ECC_97: m_keySize = 97;break;
		case ECC_100: m_keySize = 100;break;
		case ECC_120: m_keySize = 120;break;
		case ECC_131: m_keySize = 131;break;
		case ECC_141: m_keySize = 141;break;
		case ECC_161: m_keySize = 161;break;
		}
	}

	KEY_TYPE GetKeyType()
	{
		return m_keyType;
	}

	KEY_TYPE m_keyType;
	int m_keySize;
	bigint m_key;
	bool isPrivate;
};

class SignerImpl {
public:
	SignerImpl()
	{
		m_hashBits = 0; // thus select ECDSA as the signing algorithm
	}

	~SignerImpl()
	{

	}

	static bigint Hash(const bigint & x, const bigint & p)
	{
		unsigned int n = ( x.bits() + ( ( sizeof(unsigned) << 3 ) - 1 ) ) / ( sizeof( unsigned ) << 3 );
		unsigned int * a = (unsigned *)_alloca(n * sizeof(unsigned));
		unsigned int h[ 5 ];

		x.store(a, n);

		SHA1 sha;

#ifndef LICENSING_BIG_ENDIAN
		sha.Update((unsigned char *)a, (x.bits() + 7) >> 3);
#else
		int nb = (x.bits() + 7) >> 3;
		unsigned char * buf = (unsigned char *)_alloca(nb);
		UintArrayToByteArray(a, x.bits(), buf);
		sha.Update(buf, nb);
#endif

		sha.Final();

#ifndef LICENSING_BIG_ENDIAN
		sha.GetHash((unsigned char *)h);
#else
		unsigned char bh[20];
		sha.GetHash(bh);
		ByteArrayToUintArray(bh, 20, h);
#endif

		bigint r;
		r.load( h, 5 );

		return r % p;
	}

	void SetHashSize(int hashBits)
	{
		m_hashBits = hashBits;
	}

	void SetPrivateKey(const KeyImpl & privKey)
	{
		m_privateKey = privKey;
	}

	void Sign(const void * msg, int msgLen, void * sigBuf, int * sigLen, int * sigLenBits)
	{
		full_curve_parameter param(eccCurveTable[ m_privateKey.m_keyType ]);
		ECC_BASE ecc(param);
		bigint message;
		bigint temp;
		bigint_pair signature;

		*sigLenBits = (m_hashBits && m_hashBits < m_privateKey.m_keySize) ? m_hashBits + m_privateKey.m_keySize : m_privateKey.m_keySize << 1;
		*sigLen = (*sigLenBits + 7) >> 3;

		unsigned * buf = (unsigned *)_alloca(msgLen + *sigLen + sizeof(unsigned) - 1);
		
		// take care of endianess here, do not do just a memcpy()
		memcpy(buf, msg, msgLen);
		memset((char *)buf + msgLen, 0, sizeof(unsigned) - 1);
		
		unsigned msgHash[5];

		SHA1 sha;
		sha.Update((unsigned char *)msg, msgLen);
		sha.Final();

#ifndef LICENSING_BIG_ENDIAN
		sha.GetHash((unsigned char *)msgHash);
#else
		unsigned char msgHashBuf[20];
		sha.GetHash(msgHashBuf);
		ByteArrayToUintArray(msgHashBuf, 20, msgHash);
#endif

		message.load(msgHash, 5);

		signature = (m_hashBits && m_hashBits < m_privateKey.m_keySize) ? ecc.schnorr_sign(message, m_privateKey.m_key, Hash, m_hashBits) : ecc.dsa_sign(message, m_privateKey.m_key, Hash);

		temp = (signature.r << m_privateKey.m_keySize); temp += signature.s;

		unsigned int n = (unsigned)((*sigLen + sizeof(unsigned) - 1) / sizeof(unsigned));

		temp.store(buf, n);

#ifndef LICENSING_BIG_ENDIAN
		// take care of endianess here
		memcpy(sigBuf, buf, *sigLen);
#else
		UintArrayToByteArray(buf, *sigLenBits, (unsigned char *)sigBuf);
#endif
	}

private:
	KeyImpl m_privateKey;
	int m_hashBits;
};

class VerifierImpl {
public:
	VerifierImpl()
	{
		m_hashBits = 0;
	}

	~VerifierImpl()
	{

	}

	static bigint Hash(const bigint & x, const bigint & p)
	{
		int n = ( x.bits() + ( ( sizeof(unsigned) << 3 ) - 1 ) ) / ( sizeof( unsigned ) << 3 );
		unsigned int * a = (unsigned *)_alloca(n * sizeof(unsigned));
		unsigned int h[ 5 ];

		x.store(a, n);

		SHA1 sha;
		
#ifndef LICENSING_BIG_ENDIAN
		sha.Update((unsigned char *)a, (x.bits() + 7) >> 3);
#else
		int nb = (x.bits() + 7) >> 3;
		unsigned char * buf = (unsigned char *)_alloca(nb);
		UintArrayToByteArray(a, x.bits(), buf);
		sha.Update(buf, nb);
#endif
		
		sha.Final();

#ifndef LICENSING_BIG_ENDIAN
		sha.GetHash((unsigned char *)h);
#else
		unsigned char bh[20];
		sha.GetHash(bh);
		ByteArrayToUintArray(bh, 20, h);
#endif

		bigint r;
		r.load( h, 5 );

		return r % p;
	}

	void SetHashSize(int hashBits)
	{
		m_hashBits = hashBits;
	}

	void SetPublicKey(const KeyImpl & pubKey)
	{
		m_publicKey = pubKey;
	}

	bool Verify(const void * msg, int msgLen, const void * sig, int sigLen, int sigLenBits)
	{
		full_curve_parameter param(eccCurveTable[ m_publicKey.m_keyType ]);
		ECC_BASE ecc(param);
		bigint message;
		bigint_pair signature;

		int msgBufLen = (msgLen +  sizeof(unsigned) - 1) / sizeof(unsigned);
		int sigBufLen = (sigLen +  sizeof(unsigned) - 1) / sizeof(unsigned);

		unsigned * msgBuf = (unsigned *)_alloca(msgBufLen * sizeof(unsigned));
		unsigned * sigBuf = (unsigned *)_alloca(sigBufLen * sizeof(unsigned));

#ifndef LICENSING_BIG_ENDIAN
		memset(msgBuf, 0, msgBufLen * sizeof(unsigned));
		memcpy(msgBuf, msg, msgLen);
		memset(sigBuf, 0, sigBufLen * sizeof(unsigned));
		memcpy(sigBuf, sig, sigLen);
#else
		ByteArrayToUintArray((unsigned char *)msg, msgLen, msgBuf);
		ByteArrayToUintArray((unsigned char *)sig, sigLen, sigBuf);
#endif

		unsigned int msgHash[5];

		SHA1 sha;
		sha.Update((unsigned char *)msg, msgLen);
		sha.Final();

#ifndef LICENSING_BIG_ENDIAN
		sha.GetHash((unsigned char *)msgHash);
#else
		unsigned char byteHash[20];
		
		sha.GetHash(byteHash);

		ByteArrayToUintArray(byteHash, 20, msgHash);
#endif
		message.load(msgHash, 5);

		signature.r.load(sigBuf, sigBufLen); 

		signature.r >>= m_publicKey.m_keySize; signature.r &= (pow2((unsigned)(sigLenBits - m_publicKey.m_keySize)) - 1);
		
		signature.s.load(sigBuf, sigBufLen); 

		signature.s &= (pow2(m_publicKey.m_keySize) - 1);

		return (m_hashBits && m_hashBits < m_publicKey.m_keySize) ? ecc.schnorr_verify(message, signature, m_publicKey.m_key, Hash, m_hashBits) : ecc.dsa_verify(message, signature, m_publicKey.m_key, Hash);
	}

private:
	int m_hashBits;
	KeyImpl m_publicKey;
};

void GenerateKeyPair(KEY_TYPE keyType, Key **privKey, Key **pubKey)
{
	full_curve_parameter param(eccCurveTable[ keyType ]);
	ECC_BASE ecc(param);
	Key * privateKey = new Key(), 
		* publicKey = new Key();

	privateKey->m_Impl.m_key = ecc.create_private_key();
	privateKey->m_Impl.SetKeyType(keyType);
	privateKey->m_Impl.isPrivate = true;

	publicKey->m_Impl.m_key = ecc.create_public_key(privateKey->m_Impl.m_key);
	publicKey->m_Impl.SetKeyType(keyType);

	*privKey = privateKey;
	*pubKey = publicKey;
}

Key::Key():
m_Impl( *new KeyImpl() )
{

}

Key::Key(const void * keyBuf, int bufLen, KEY_TYPE keyType):
m_Impl( *new KeyImpl(keyBuf, bufLen, keyType) )
{

}

Key::Key(const char * base64Key, KEY_TYPE keyType):
m_Impl( *new KeyImpl(base64Key, keyType))
{

}

Key::~Key()
{
	delete & m_Impl;
}

void Key::Load(const void * keyData, int keyLen, KEY_TYPE keyType)
{
	m_Impl.Load(keyData, keyLen, keyType);
}

bool Key::Store(void * buf, int * bufLen) const
{
	return m_Impl.Store(buf, bufLen);
}

const char * Key::ToString()
{
	
	static string base64key;

	int len = 2 + ((m_Impl.m_key.bits() + 7) >> 3);
	unsigned char * buf = (unsigned char *)_alloca(len);
	Store(buf, &len);

	BASE64 base64;

	auto base64Buf = base64.encode(buf, len, true);
	if (base64Buf.empty())
		throw new LicensingException(STATUS_GENERIC_ERROR, "could not encode buffer to base64");

	base64key = base64Buf.c_str();

	return base64key.c_str();
}

KEY_TYPE Key::GetKeyType()
{
	return m_Impl.GetKeyType();
}

const Key & Key::operator = (const Key & key)
{
	m_Impl = key.m_Impl;
	return (const Key &)(*this);
}

Signer::Signer():
m_Impl(*new SignerImpl())
{

}

Signer::~Signer()
{
	delete & m_Impl;
}

void Signer::SetHashSize(int hashBits)
{
	m_Impl.SetHashSize(hashBits);
}

void Signer::SetPrivateKey(const Key * privKey)
{
	m_Impl.SetPrivateKey(privKey->m_Impl);
}

void Signer::Sign(const void * msg, int msgLen, void * sigBuf, int * sigLen, int * sigLenBits)
{
	m_Impl.Sign(msg, msgLen, sigBuf, sigLen, sigLenBits);
}

Verifier::Verifier():
m_Impl( *new VerifierImpl() )
{

}

Verifier::~Verifier()
{
	delete & m_Impl;
}

void Verifier::SetHashSize(int hashBits)
{
	m_Impl.SetHashSize(hashBits);
}

void Verifier::SetPublicKey(const Key * pubKey)
{
	m_Impl.SetPublicKey(pubKey->m_Impl);
}

bool Verifier::Verify(const void * msg, int msgLen, const void * sig, int sigLen, int sigLenBits)
{
	return m_Impl.Verify(msg, msgLen, sig, sigLen, sigLenBits);
}

}
