// idea.cpp - written and placed in the public domain by Wei Dai

#include "pch.h"
#include "idea.h"
#include "misc.h"

NAMESPACE_BEGIN(CryptoPP)

static const int IDEA_KEYLEN=(6*IDEA::ROUNDS+4);  // key schedule length in # of word16s

#define low16(x) ((x)&0xffff)	// compiler should be able to optimize this away if word is 16 bits
#define high16(x) ((x)>>16)

// should use an inline function but macros are still faster in MSVC 4.0
#define DirectMUL(a,b)					\
{										\
	assert(b <= 0xffff);				\
										\
	word32 p=(word32)low16(a)*b;		\
										\
	if (p)								\
	{									\
		p = low16(p) - high16(p);		\
		a = (word)p - (word)high16(p);	\
	}									\
	else								\
		a = 1-a-b;						\
}

#ifdef IDEA_LARGECACHE
bool IDEA::Base::tablesBuilt = false;
word16 IDEA::Base::log[0x10000];
word16 IDEA::Base::antilog[0x10000];

void IDEA::Base::BuildLogTables()
{
	if (tablesBuilt)
		return;
	else
	{
		tablesBuilt = true;
		
		word x=1;
		word32 i;
		
		for (i=0; i<0x10000; i++)
		{
			antilog[i] = (word16)x;
			DirectMUL(x, 3);
		}
		
		for (i=0; i<0x10000; i++)
			log[antilog[i]] = (word16)i;
	}
}

void IDEA::Base::LookupKeyLogs()
{
	word* Z=key;
	int r=ROUNDS;
	do
	{
		Z[0] = log[Z[0]];
		Z[3] = log[Z[3]];
		Z[4] = log[Z[4]];
		Z[5] = log[Z[5]];
		Z+=6;
	} while (--r);
	Z[0] = log[Z[0]];
	Z[3] = log[Z[3]];
}

inline void IDEA::Base::LookupMUL(word &a, word b)
{
	a = antilog[low16(log[low16(a)]+b)];
}
#endif // IDEA_LARGECACHE

void IDEA::Base::UncheckedSetKey(CipherDir direction, const byte *userKey, unsigned int length)
{
	AssertValidKeyLength(length);
	
#ifdef IDEA_LARGECACHE
	BuildLogTables();
#endif
	
	EnKey(userKey);
	
	if (direction==DECRYPTION)
		DeKey();
	
#ifdef IDEA_LARGECACHE
	LookupKeyLogs();
#endif
}

void IDEA::Base::EnKey (const byte *userKey)
{
	unsigned int i;
	
	for (i=0; i<8; i++)
		m_key[i] = ((word)userKey[2*i]<<8) | userKey[2*i+1];
	
	for (; i<IDEA_KEYLEN; i++)
	{
		unsigned int j = RoundDownToMultipleOf(i,8U)-8;
		m_key[i] = low16((m_key[j+(i+1)%8] << 9) | (m_key[j+(i+2)%8] >> 7));
	}
}

static word MulInv(word x)
{
	word y=x;
	for (unsigned i=0; i<15; i++)
	{
		DirectMUL(y,low16(y));
		DirectMUL(y,x);
	}
	return low16(y);
}

static inline word AddInv(word x)
{
	return low16(0-x);
}

void IDEA::Base::DeKey()
{
	FixedSizeSecBlock<word, 6*ROUNDS+4> tempkey;
	unsigned int i;

	for (i=0; i<ROUNDS; i++)
	{
		tempkey[i*6+0] = MulInv(m_key[(ROUNDS-i)*6+0]);
		tempkey[i*6+1] = AddInv(m_key[(ROUNDS-i)*6+1+(i>0)]);
		tempkey[i*6+2] = AddInv(m_key[(ROUNDS-i)*6+2-(i>0)]);
		tempkey[i*6+3] = MulInv(m_key[(ROUNDS-i)*6+3]);
		tempkey[i*6+4] =        m_key[(ROUNDS-1-i)*6+4];
		tempkey[i*6+5] =        m_key[(ROUNDS-1-i)*6+5];
	}

	tempkey[i*6+0] = MulInv(m_key[(ROUNDS-i)*6+0]);
	tempkey[i*6+1] = AddInv(m_key[(ROUNDS-i)*6+1]);
	tempkey[i*6+2] = AddInv(m_key[(ROUNDS-i)*6+2]);
	tempkey[i*6+3] = MulInv(m_key[(ROUNDS-i)*6+3]);

	m_key = tempkey;
}

#ifdef IDEA_LARGECACHE
#define MUL(a,b) LookupMUL(a,b)
#else
#define MUL(a,b) DirectMUL(a,b)
#endif

void IDEA::Base::ProcessAndXorBlock(const byte *inBlock, const byte *xorBlock, byte *outBlock) const
{
	typedef BlockGetAndPut<word16, BigEndian> Block;

	const word *key = m_key;
	word x0,x1,x2,x3,t0,t1;
	Block::Get(inBlock)(x0)(x1)(x2)(x3);

	for (unsigned int i=0; i<ROUNDS; i++)
	{
		MUL(x0, key[i*6+0]);
		x1 += key[i*6+1];
		x2 += key[i*6+2];
		MUL(x3, key[i*6+3]);
		t0 = x0^x2; 
		MUL(t0, key[i*6+4]);
		t1 = t0 + (x1^x3);
		MUL(t1, key[i*6+5]);
		t0 += t1;
		x0 ^= t1;
		x3 ^= t0;
		t0 ^= x1;
		x1 = x2^t1;
		x2 = t0;
	}

	MUL(x0, key[ROUNDS*6+0]);
	x2 += key[ROUNDS*6+1];
	x1 += key[ROUNDS*6+2];
	MUL(x3, key[ROUNDS*6+3]);

	Block::Put(xorBlock, outBlock)(x0)(x2)(x1)(x3);
}

NAMESPACE_END
