context: using System;
  using System.Text;
  
  using Nequeo.Cryptography.Key.Crypto.Parameters;
  using Nequeo.Cryptography.Key.Utilities;
  
  namespace Nequeo.Cryptography.Key.Crypto.Engines
  {
  	/**
  	* Implementation of Daniel J. Bernstein's Salsa20 stream cipher, Snuffle 2005
  	*/
  	public class Salsa20Engine
  		: IStreamCipher
  	{
  		/** Constants */
  		private const int stateSize = 16; // 16, 32 bit ints = 64 bytes
  
  		private readonly static byte[]
  			sigma = Strings.ToAsciiByteArray("expand 32-byte k"),
              tau = Strings.ToAsciiByteArray("expand 16-byte k");
  
  		/*
  		* variables to hold the state of the engine
  		* during encryption and decryption
  		*/
  		private int		index = 0;
  		private int[]	engineState = new int[stateSize]; // state
  		private int[]	x = new int[stateSize] ; // internal buffer
  		private byte[]	keyStream   = new byte[stateSize * 4], // expanded state, 64 bytes
  						workingKey  = null,
  						workingIV   = null;
  		private bool	initialised = false;
  
  		/*
  		* internal counter
  		*/
  		private int cW0, cW1, cW2;
  
  		/**
  		* initialise a Salsa20 cipher.
  		*
  		* @param forEncryption whether or not we are for encryption.
  		* @param params the parameters required to set up the cipher.
  		* @exception ArgumentException if the params argument is
  		* inappropriate.
  		*/
  		public void Init(
  			bool				forEncryption, 
  			ICipherParameters	parameters)
  		{
  			/* 
  			* Salsa20 encryption and decryption is completely
  			* symmetrical, so the 'forEncryption' is 
  			* irrelevant. (Like 90% of stream ciphers)
  			*/
  
  			ParametersWithIV ivParams = parameters as ParametersWithIV;
  
  			if (ivParams == null)
  				throw new ArgumentException("Salsa20 Init requires an IV", "parameters");
  
  			byte[] iv = ivParams.GetIV();
  
  			if (iv == null || iv.Length != 8)
  				throw new ArgumentException("Salsa20 requires exactly 8 bytes of IV");
  
  			KeyParameter key = ivParams.Parameters as KeyParameter;
  
  			if (key == null)
  				throw new ArgumentException("Salsa20 Init requires a key", "parameters");
  
  			workingKey = key.GetKey();
  			workingIV = iv;
  
  			setKey(workingKey, workingIV);
  		}
  
  		public string AlgorithmName
  		{
  			get { return "Salsa20"; }
  		}
  
  		public byte ReturnByte(
  			byte input)
  		{
  			if (limitExceeded())
  			{
  				throw new MaxBytesExceededException("2^70 byte limit per IV; Change IV");
  			}
  
  			if (index == 0)
  			{
  				salsa20WordToByte(engineState, keyStream);
  				engineState[8]++;
  				if (engineState[8] == 0)
  				{
  					engineState[9]++;
  				}
  			}
  			byte output = (byte)(keyStream[index]^input);
  			index = (index + 1) & 63;
  	    
  			return output;
  		}
  
  		public void ProcessBytes(
  			byte[]	inBytes, 
  			int		inOff, 
  			int		len, 
  			byte[]	outBytes, 
  			int		outOff)
  		{
  			if (!initialised)
  			{
  				throw new InvalidOperationException(AlgorithmName + " not initialised");
  			}
  
  			if ((inOff + len) > inBytes.Length)
  			{
  				throw new DataLengthException("input buffer too short");
  			}
  
  			if ((outOff + len) > outBytes.Length)
  			{
  				throw new DataLengthException("output buffer too short");
  			}
  	        
  			if (limitExceeded(len))
  			{
  				throw new MaxBytesExceededException("2^70 byte limit per IV would be exceeded; Change IV");
  			}
  
  			for (int i = 0; i < len; i++)
  			{
  				if (index == 0)
  				{
  					salsa20WordToByte(engineState, keyStream);
  					engineState[8]++;
  					if (engineState[8] == 0)
  					{
  						engineState[9]++;
  					}
  				}
  				outBytes[i+outOff] = (byte)(keyStream[index]^inBytes[i+inOff]);
  				index = (index + 1) & 63;
  			}
  		}
  
  		public void Reset()
  		{
  			setKey(workingKey, workingIV);
  		}
  
  		// Private implementation
  
  		private void setKey(byte[] keyBytes, byte[] ivBytes)
  		{
  			workingKey = keyBytes;
  			workingIV  = ivBytes;
  
  			index = 0;
  			resetCounter();
  			int offset = 0;
  			byte[] constants;
  
  			// Key
  			engineState[1] = byteToIntLittle(workingKey, 0);
  			engineState[2] = byteToIntLittle(workingKey, 4);
  			engineState[3] = byteToIntLittle(workingKey, 8);
  			engineState[4] = byteToIntLittle(workingKey, 12);
  
  			if (workingKey.Length == 32)
  			{
  				constants = sigma;
  				offset = 16;
  			}
  			else
  			{
  				constants = tau;
  			}
  	        
  			engineState[11] = byteToIntLittle(workingKey, offset);
  			engineState[12] = byteToIntLittle(workingKey, offset+4);
  			engineState[13] = byteToIntLittle(workingKey, offset+8);
  			engineState[14] = byteToIntLittle(workingKey, offset+12);
  			engineState[0 ] = byteToIntLittle(constants, 0);
  			engineState[5 ] = byteToIntLittle(constants, 4);
  			engineState[10] = byteToIntLittle(constants, 8);
  			engineState[15] = byteToIntLittle(constants, 12);
  	        
  			// IV
  			engineState[6] = byteToIntLittle(workingIV, 0);
  			engineState[7] = byteToIntLittle(workingIV, 4);
  			engineState[8] = engineState[9] = 0;
  	        
  			initialised = true;
  		}
  	    
  		/**
  		* Salsa20 function
  		*
  		* @param   input   input data
  		*
  		* @return  keystream
  		*/    
  		private void salsa20WordToByte(
  			int[]	input,
  			byte[]	output)
  		{
  			Array.Copy(input, 0, x, 0, input.Length);
  
  
gt: 			for (int i = 0; i < 10; i++)