//
//		Title: Tiger Hash for C#
//	   Author: captnmac of Software Union
// ----------------------------------------
//  Description: A balanced-optimized safe native C# implementation
//			   of the cryptographic TIGER hash algorithm from 1995.
//			   invokable thru dotnet's HashAlgorithm class.
// Improvements: Streamlined and now generates the s-boxes on-demand.
//	  License: Common Development and Distribution License (CDDL)
// #################################################################


using System;
using HashAlgorithm=System.Security.Cryptography.HashAlgorithm;
using System.Runtime.InteropServices;
using Encoding=System.Text.Encoding;


namespace SoftwareUnion
{

	abstract public class BlockHashAlgorithm : HashAlgorithm
	{

		private byte[] ba_PartialBlockBuffer;
		private int i_PartialBlockFill;
		
		protected int i_InputBlockSize;
		protected long l_TotalBytesProcessed;


		/// <summary>Initializes a new instance of the BlockHashAlgorithm class.</summary>
		/// <param name="blockSize">The size in bytes of an individual block.</param>
		protected BlockHashAlgorithm(int blockSize,int hashSize) : base()
		{
			this.i_InputBlockSize = blockSize;
			this.HashSizeValue = hashSize;
			ba_PartialBlockBuffer = new byte[BlockSize];
		}


		/// <summary>Initializes the algorithm.</summary>
		/// <remarks>If this function is overriden in a derived class, the new function should call back to
		/// this function or you could risk garbage being carried over from one calculation to the next.</remarks>
		public override void Initialize()
		{	//abstract: base.Initialize();
			l_TotalBytesProcessed = 0;
			i_PartialBlockFill = 0;
			if(ba_PartialBlockBuffer==null) ba_PartialBlockBuffer=new byte[BlockSize];
		}


		/// <summary>The size in bytes of an individual block.</summary>
		public int BlockSize
		{
			get { return i_InputBlockSize; }
		}

		/// <summary>The number of bytes currently in the buffer waiting to be processed.</summary>
		public int BufferFill
		{
			get { return i_PartialBlockFill; }
		}

		
		/// <summary>Performs the hash algorithm on the data provided.</summary>
		/// <param name="array">The array containing the data.</param>
		/// <param name="ibStart">The position in the array to begin reading from.</param>
		/// <param name="cbSize">How many bytes in the array to read.</param>
		protected override void HashCore(byte[] array,int ibStart,int cbSize)
		{
			int i;

			// Use what may already be in the buffer.
			if(BufferFill > 0)
			{
				if(cbSize+BufferFill < BlockSize)
				{
					// Still don't have enough for a full block, just store it.
					Array.Copy(array,ibStart,ba_PartialBlockBuffer,BufferFill,cbSize);
					i_PartialBlockFill += cbSize;
					return;
				}
				else
				{
					// Fill out the buffer to make a full block, and then process it.
					i = BlockSize - BufferFill;
					Array.Copy(array,ibStart,ba_PartialBlockBuffer,BufferFill,i);
					ProcessBlock(ba_PartialBlockBuffer,0,1); l_TotalBytesProcessed += BlockSize;
					i_PartialBlockFill = 0; ibStart += i; cbSize -= i;
				}
			}

			// For as long as we have full blocks, process them.
			if(cbSize>=BlockSize)
			{	ProcessBlock(array,ibStart,cbSize/BlockSize);
				l_TotalBytesProcessed+=cbSize-cbSize%BlockSize;
			}
			/*for(i=0; i < (cbSize-cbSize%BlockSize); i+=BlockSize)
			{	ProcessBlock(array,ibStart + i,1);
				count += BlockSize;
			}*/

			// If we still have some bytes left, store them for later.
			int bytesLeft = cbSize % BlockSize;
			if(bytesLeft != 0)
			{
				Array.Copy(array,((cbSize - bytesLeft) + ibStart),ba_PartialBlockBuffer,0,bytesLeft);
				i_PartialBlockFill = bytesLeft;
			}
		}


		/// <summary>Performs any final activities required by the hash algorithm.</summary>
		/// <returns>The final hash value.</returns>
		protected override byte[] HashFinal()
		{
			return ProcessFinalBlock(ba_PartialBlockBuffer,0,i_PartialBlockFill);
		}


		/// <summary>Process a block of data.</summary>
		/// <param name="inputBuffer">The block of data to process.</param>
		/// <param name="inputOffset">Where to start in the block.</param>
		protected abstract void ProcessBlock(byte[] inputBuffer,int inputOffset,int inputLength);


		/// <summary>Process the last block of data.</summary>
		/// <param name="inputBuffer">The block of data to process.</param>
		/// <param name="inputOffset">Where to start in the block.</param>
		/// <param name="inputCount">How many bytes need to be processed.</param>
		/// <returns>The results of the completed hash calculation.</returns>
		protected abstract byte[] ProcessFinalBlock(byte[] inputBuffer,int inputOffset,int inputCount);

	}


	public class Tiger:BlockHashAlgorithm
	{
		const string AUTHOR_INFO="Tiger - A Fast New Hash Function, by Ross Anderson and Eli Biham";

		const ulong A1_INIT=0x0123456789abcdefUL, A2_INIT=0xfedcba9876543210UL, A3_INIT=0xf096a5b4c3b2e187UL,
			A1_REF=0x4d363c7408f69f3fUL, A2_REF=0x7f5158f44eca195eUL, A3_REF=0x91716577a6381050UL;
		
		// state registers
		private static ulong[] u_t0,u_t1,u_t2,u_t3;
		private ulong x0,x1,x2,x3,x4,x5,x6,x7;
		private ulong[] _x;
		private ulong_byte8 _a1,_a2,_a3;


		[StructLayout(LayoutKind.Explicit)]
		private struct ulong_byte8
		{	[FieldOffset(0)] public ulong ul;
			
			[FieldOffset(0)] public byte b0; [FieldOffset(1)] public byte b1;
			[FieldOffset(2)] public byte b2; [FieldOffset(3)] public byte b3;
			[FieldOffset(4)] public byte b4; [FieldOffset(5)] public byte b5;
			[FieldOffset(6)] public byte b6; [FieldOffset(7)] public byte b7;
			
			public ulong_byte8(ulong u)
				{ b0=b1=b2=b3=b4=b5=b6=b7=0; ul=u; }
			public override string ToString()
				{ return ul.ToString("x16"); }
		}

		
		public Tiger():base(64,192)
		{	Initialize();
		}

		private void ExchangeElems(ref ulong[] ua,byte uWhat,ulong uWith)
		{	ulong uMask=255UL,uDiff;

			for(var iByteOffs=0;iByteOffs<8;++iByteOffs)
			{	uDiff=uMask & (ua[uWhat]^ua[(byte)uWith]);
				ua[(byte)uWith]^=uDiff; ua[uWhat]^=uDiff;
				uWith>>=8; uMask<<=8;
			}
		}

		private void GenerateSboxes(byte[] baTmpStr)
		{	uint a,i; var utX=new [] {u_t0,u_t1,u_t2,u_t3};
			
			for(i=0;i<256;++i)
				u_t0[i]=u_t1[i]=u_t2[i]=u_t3[i]=(ulong)i*0x0101010101010101UL;
			
			for(a=i=0;;)
			{	ProcessBlock(baTmpStr,0,1);
				ExchangeElems(ref utX[i&3],(byte)(i>>2),_a1.ul); ++i;
				ExchangeElems(ref utX[i&3],(byte)(i>>2),_a2.ul); ++i;
if(i>=5120) break;
				ExchangeElems(ref utX[i&3],(byte)(i>>2),_a3.ul); ++i;
			}
		}
		
		public override void Initialize()
		{
			base.Initialize();
			
			if(_x==null) _x=new ulong[8];
			if(_x.Length!=8) Array.Resize(ref _x,8);

			if(u_t0==null || u_t1==null || u_t2==null || u_t3==null)
			{	var x=256; //u__t0.Length;
				u_t0=new ulong[x]; u_t1=new ulong[x];
				u_t2=new ulong[x]; u_t3=new ulong[x]; 

				_a1.ul=A1_INIT; _a2.ul=A2_INIT; _a3.ul=A3_INIT;
				var baEntropy=Encoding.ASCII.GetBytes(AUTHOR_INFO);
				GenerateSboxes(baEntropy);
				
				_a1.ul=A1_INIT; _a2.ul=A2_INIT; _a3.ul=A3_INIT;
				ProcessBlock(baEntropy,0,1);
				if(_a1.ul!=A1_REF || _a2.ul!=A2_REF || _a3.ul!=A3_REF)
					throw new TypeInitializationException(AUTHOR_INFO,null);
			}
			_a1.ul=A1_INIT; _a2.ul=A2_INIT; _a3.ul=A3_INIT;
		}

		private void Pass(ref ulong_byte8 x,ref ulong_byte8 y,ref ulong_byte8 z,byte f)
		{	ulong_byte8 w;

			for(var i=0;i<8;++i)
			{	z.ul ^= _x[i];
				x.ul -= u_t0[z.b0] ^ u_t1[z.b2] ^ u_t2[z.b4] ^ u_t3[z.b6];
				y.ul += u_t3[z.b1] ^ u_t2[z.b3] ^ u_t1[z.b5] ^ u_t0[z.b7];
				y.ul *= f;

				w.ul=x.ul; x.ul=y.ul; y.ul=z.ul; z.ul=w.ul;
			}
		}

		private void KeySchedule()
		{
			x0 -= x7 ^ 0xA5A5A5A5A5A5A5A5UL;
			x1 ^= x0; x2 += x1; x3 -= x2 ^ ((~x1) << 19);
			x4 ^= x3; x5 += x4; x6 -= x5 ^ ((~x4) >> 23);
			x7 ^= x6; x0 += x7; x1 -= x0 ^ ((~x7) << 19);
			x2 ^= x1; x3 += x2; x4 -= x3 ^ ((~x2) >> 23);
			x5 ^= x4; x6 += x5;
			x7 -= x6 ^ 0x0123456789ABCDEFUL;

			_x[0]=x0; _x[1]=x1; _x[2]=x2; _x[3]=x3;
			_x[4]=x4; _x[5]=x5; _x[6]=x6; _x[7]=x7;
		}

		protected override void ProcessBlock(byte[] inputBuffer,int inputOffset,int iBlkCount)
		{	ulong_byte8 a=_a1, b=_a2, c=_a3;

			for(int i;iBlkCount>0;--iBlkCount)
			{	
				// load plain text
				for(i=0;i<8;++i,inputOffset+=8)
					_x[i]=BitConverter.ToUInt64(inputBuffer,inputOffset);

				x0=_x[0]; x1=_x[1]; x2=_x[2]; x3=_x[3];
				x4=_x[4]; x5=_x[5]; x6=_x[6]; x7=_x[7];

				Pass(ref a,ref b,ref c,5); 
				KeySchedule();
				Pass(ref a,ref b,ref c,7); 
				KeySchedule();
				Pass(ref a,ref b,ref c,9); 

				// feed forward
				_a1.ul=a.ul^=_a1.ul; _a2.ul=b.ul-=_a2.ul; _a3.ul=c.ul+=_a3.ul;
			}
		}

		protected override byte[] ProcessFinalBlock(byte[] inputBuffer,int inputOffset,int inputCount)
		{	int paddingSize;

			// how much padding is needed after the last byte of cargo
			paddingSize = (int)(((ulong)inputCount + (ulong)l_TotalBytesProcessed) % (ulong)BlockSize);
			paddingSize = (BlockSize-8) - paddingSize;
			if(paddingSize<1) paddingSize += BlockSize;

			// create the final/padded block/blocks
			if(inputOffset>0 && inputCount>0) Array.Copy(inputBuffer,inputOffset,inputBuffer,0,inputCount);
			inputOffset=0;

			Array.Clear(inputBuffer,inputCount,BlockSize-inputCount);
			inputBuffer[inputCount] = 0x01; //0x80;
			ulong msg_bit_length = ((ulong)l_TotalBytesProcessed + (ulong)inputCount)<<3;

			if(inputCount+8 >= BlockSize)
			{
				if(inputBuffer.Length < 2*BlockSize) Array.Resize(ref inputBuffer,2*BlockSize);
				ProcessBlock(inputBuffer,inputOffset,1);
				inputOffset+=BlockSize; inputCount-=BlockSize;
			}

			for(inputCount=inputOffset+BlockSize-sizeof(ulong);msg_bit_length!=0;
					inputBuffer[inputCount]=(byte)msg_bit_length,msg_bit_length>>=8,++inputCount) ;
			ProcessBlock(inputBuffer,inputOffset,1);


			HashValue=new byte[HashSizeValue>>3];
			Array.Copy(BitConverter.GetBytes(_a1.ul),0,HashValue,0,8);
			Array.Copy(BitConverter.GetBytes(_a2.ul),0,HashValue,8,8);
			Array.Copy(BitConverter.GetBytes(_a3.ul),0,HashValue,16,8);
			
			return HashValue;
		}

	}

}
