// Copyright Chris Lomont 2009                                                                                                              
// For bugs or comments, contact me through www.lomont.org                                                                                  
// C#, Visual Studio 2008                                                                                                                   
using System;                                                                                                                               
using System.Diagnostics;                                                                                                                   
using System.Collections.Generic;                                                                                                           
                                                                                                                                            
namespace Lomont{ namespace Crypto                                                                                                          
    {                                                                                                                                       
    // todo - implement rest of spec: tree functions, etc.                                                                                  
    // todo - walk code looking for endian problems, use BitConverter.IsLittleEndian to fix?                                                
    // todo - rewrite for speed                                                                                                             
    // todo - comment throroughly, give usage cases, polish                                                                                 
    // todo - see javascript at http://www.h2database.com/skein/index.html for a way to do more inplace operations                          
    // todo -change interface uint, int, ulong, etc., to make class nicely usuable                                                          
                                                                                                                                            
                                                                                                                                            
    /// <summary>                                                                                                                           
    /// The Skein hash function                                                                                                             
    /// Create a class using internal length 256, 512, or 1024 bits                                                                         
    /// Then use the Hash function to create hashes, usually of the                                                                         
    /// same size.                                                                                                                          
    /// </summary>                                                                                                                          
    public class Skein : Threefish                                                                                                          
        {                                                                                                                                   
        /// <summary>                                                                                                                       
        /// Create a Skein hash                                                                                                             
        /// Length is 256, 512, or 1024 bits for block/key size.                                                                            
        /// </summary>                                                                                                                      
        /// <param name="length">The length og the internal state: 256, 512, or 1024 bits</param>                                           
        public Skein(uint length) : base(length)                                                                                            
            {                                                                                                                               
            if ((length != 256) && (length != 512) && (length != 1024))                                                                     
                throw new ArgumentOutOfRangeException("stateSize""Must be one of 256, 512, 1024.");                                       
            Nb = length / 8;                                                                                                                
            }                                                                                                                               
                                                                                                                                            
        /// <summary>                                                                                                                       
        /// Given an array of byte data, compute the hash                                                                                   
        /// </summary>                                                                                                                      
        /// <param name="data">The data to hash.</param>                                                                                    
        /// <param name="No">The number of bits desired in return. Usually the same as the length used to create this instance.</param>     
        /// <returns>The hashed byte data</returns>                                                                                         
        public byte[] Hash(byte[] data, uint No)                                                                                            
            {                                                                                                                               
            Debug.Assert((Nb == 32) || (Nb == 64) || (Nb == 128));                                                                          
            byte [] M = data;   // alias for the data                                                                                       
            byte [] C = new byte[32]; // configuration                                                                                      
            SkeinByteConverter.ToBytes(0x33414853, 4).CopyTo(C, 0);  // ASCII 'SHA3' // todo - check endianess                              
            SkeinByteConverter.ToBytes(1, 2).CopyTo(C, 4);           // 0,1: version 1, others reserved // todo -check endianess?           
            SkeinByteConverter.ToBytes(No, 8).CopyTo(C, 8);          // output bitLength // todo - endianess?                               
            // C[16] = 0;   // Tree leaf size Yl, Tree fan out Yf, Tree height Ym                                                           
            // C[19] to C[31] = 0, reserved                                                                                                 
                                                                                                                                            
            byte [] K = new byte[Nb];                                                                                                       
            byte [] T = new byte[16];                                                                                                       
                                                                                                                                            
            T[15] = 4;  // Set configuration block Tcfg                                                                                     
            byte [] G0 = UBI(K, C, T, Nb * 8);                                                                                              
            T[15] = 48; // set message block Tmsg                                                                                           
            byte [] G1 = UBI(G0, M, T, 8 * (ulong)M.Length);                                                                                
            byte[] H = Output(G1, No);                                                                                                      
            return H;                                                                                                                       
            }                                                                                                                               
                                                                                                                                            
        #region Implementation                                                                                                              
                                                                                                                                            
        /// <summary>                                                                                                                       
        /// Number of bytes 32, 64, or 128                                                                                                  
        /// </summary>                                                                                                                      
        ulong Nb = 0;                                                                                                                       
                                                                                                                                            
        /// <summary>                                                                                                                       
        /// Perform the UBI compression on G, return compression.                                                                           
        /// </summary>                                                                                                                      
        /// <param name="G">From spec</param>                                                                                               
        /// <param name="M">Message from spec</param>                                                                                       
        /// <param name="T">Tweak from spec</param>                                                                                         
        /// <param name="bits">The number of bits to hash</param>                                                                           
        /// <returns>The byte array</returns>                                                                                               
        byte [] UBI(byte [] G, byte [] M, byte [] Ts, ulong bits)                                                                           
            { // todo need bits <= 2^99 - 8, this only holds 2^64 - 1.                                                                      
            // todo - allow M to not have all bits full, then fill according to spec                                                        
            //Debug.Assert((Nb == 32) || (Nb == 64) || (Nb == 128));                                                                        
            //if (Ts.Length != 16)                                                                                                          
            //    throw new ArgumentException("Tweak value Ts must be 16 bytes long.");                                                     
                                                                                                                                            
            int B = 0;                                                                                                                      
            byte [] Mp = M;                                                                                                                 
            if ((bits & 7) != 0)                                                                                                            
                {                                                                                                                           
                B = 1;                                                                                                                      
                Mp = new byte[M.Length];                                                                                                    
                Mp[M.Length-1] |= (byte)(1 << (int)(7-(bits & 7)));                                                                         
                }                                                                                                                           
            ulong Nm = (ulong)Mp.Length;                                                                                                    
            ulong k = (Nm + Nb - 1) / Nb; // number of blocks of bitLength Nb                                                               
            if (k == 0) k = 1; // at least one block                                                                                        
            ulong [] Mpp = new ulong[k*Nb/8];                                                                                               
            SkeinByteConverter.BytesToWords(Mp).CopyTo(Mpp, 0);                                                                             
                                                                                                                                            
            ulong [] H = SkeinByteConverter.BytesToWords(G);                                                                                
            ulong [] T = SkeinByteConverter.BytesToWords(Ts);                                                                               
            ulong [] temp = new ulong[Nb / 8];                                                                                              
                                                                                                                                            
            for (ulong i = 0; i < k ; ++i)                                                                                                  
                {                                                                                                                           
                // prepare tweak value                                                                                                      
                T[0] = Math.Min(Nm,(i+1)*Nb); // bitLength                                                                                  
                if (i == 0)                                                                                                                 
                    T[1] |= (1UL<<(126-64));  // first bit                                                                                  
                else                                                                                                                        
                    T[1] &= ~(1UL<<(126-64));                                                                                               
                if (i == k-1)                                                                                                               
                    {                                                                                                                       
                    T[1] |= (1UL<<(127-64)); // final bit                                                                                   
                    if (B != 0)                                                                                                             
                        T[1] |=  (1UL<<(119-64)); // padding                                                                                
                    }                                                                                                                       
                                                                                                                                            
                for (ulong j = 0; j < Nb / 8; ++j)                                                                                          
                    temp[j] = Mpp[j + i * Nb / 8];                                                                                          
                                                                                                                                            
                EncryptBlock(H,T,temp,0);                                                                                                   
                for (ulong j = 0; j < Nb/8; ++j)                                                                                            
                    H[j] = temp[j] ^ Mpp[j + i * Nb / 8];                                                                                   
                }                                                                                                                           
            return SkeinByteConverter.WordsToBytes(H);                                                                                      
            }                                                                                                                               
                                                                                                                                            
        /// <summary>                                                                                                                       
        /// The Output function from the spec.                                                                                              
        /// Converts the internal state to the desired output size.                                                                         
        /// </summary>                                                                                                                      
        /// <param name="G">Internal state</param>                                                                                          
        /// <param name="No">Number of desired output bytes</param>                                                                         
        /// <returns>The output bytes</returns>                                                                                             
        byte[] Output(byte [] G, ulong No)                                                                                                  
            {                                                                                                                               
            byte [] T = new byte[16];                                                                                                       
            T[15] = 63; // Set Tout                                                                                                         
                                                                                                                                            
            byte [] ans = new byte[(No+7)/8];                                                                                               
                                                                                                                                            
            ulong bitsDone = 0;                                                                                                             
            ulong i = 0;                                                                                                                    
            ulong bits = 64*(ulong)G.Length;                                                                                                
            while (bitsDone < No)                                                                                                           
                {                                                                                                                           
                byte [] O = UBI(G, SkeinByteConverter.ToBytes(i,8), T, 8*8);                                                                
                O.CopyTo(ans,(int)(bitsDone/8));                                                                                            
                ++i;                                                                                                                        
                bitsDone += bits; // todo - correct?                                                                                        
                }                                                                                                                           
            return ans;                                                                                                                     
            }                                                                                                                               
                                                                                                                                            
        #endregion   // Implementation                                                                                                      
        } // Skein                                                                                                                          
                                                                                                                                            
    /// <summary>                                                                                                                           
    /// An implementation of the Threefish encryption function.                                                                             
    /// Not sharable across threads.                                                                                                        
    /// </summary>                                                                                                                          
    public class Threefish                                                                                                                  
        {                                                                                                                                   
        /// <summary>                                                                                                                       
        /// Create a Threefish encryptor/decryptor.                                                                                         
        /// Requires bitLength in bits of key and plaintext blocks.                                                                         
        /// Length must be one of 256, 512, or 1024.                                                                                        
        /// </summary>                                                                                                                      
        /// <param name="length">Length in bits of key (256, 512, or 1024)</param>                                                          
        public Threefish(uint length)                                                                                                       
            {                                                                                                                               
            if ((length != 256) && (length != 512) && (length != 1024))                                                                     
                throw new ArgumentOutOfRangeException("Length""Threefish bitlength must be one of 256, 512, or 1024");                    
            Nw = length / 64; // number of 64 bit words                                                                                     
            Nr = 72;          // number of rounds                                                                                           
            if (Nw == 16) Nr = 80;                                                                                                          
                                                                                                                                            
            // set up tables and functions                                                                                                  
            if (Nw == 4)                                                                                                                    
                {                                                                                                                           
                R = Rtbl4;                                                                                                                  
                Permute = Permute4;                                                                                                         
                Unpermute = Unpermute4;                                                                                                     
                }                                                                                                                           
            else if (Nw == 8)                                                                                                               
                {                                                                                                                           
                R = Rtbl8;                                                                                                                  
                Permute = Permute8;                                                                                                         
                Unpermute = Unpermute8;                                                                                                     
                }                                                                                                                           
            else if (Nw == 16)                                                                                                              
                {                                                                                                                           
                R = Rtbl16;                                                                                                                 
                Permute = Permute16;                                                                                                        
                Unpermute = Unpermute16;                                                                                                    
                }                                                                                                                           
                                                                                                                                            
            Ks = new ulong[Nw + 1]; // subkey storage                                                                                       
            t = new ulong[3];       // t0,t1,t2                                                                                             
            } // constructor                                                                                                                
                                                                                                                                            
        /// <summary>                                                                                                                       
        /// Encrypt a block.                                                                                                                
        /// Requires key, plaintext, and a tweak value.                                                                                     
        /// Returns encrypted text.                                                                                                         
        /// </summary>                                                                                                                      
        /// <param name="K">Key to encrypt. Must be 32, 64, or 128 bytes and same size as plaintext P.</param>                              
        /// <param name="T">16 byte tweak value. Must be the same for decryption.</param>                                                   
        /// <param name="P">Plaintext to encrypt. Must be 32, 64, or 128 bytes and same size as key K.</param>                              
        /// <returns>The encrypted bytes.</returns>                                                                                         
        public byte[] EncryptBlock(byte[] K, byte[] T, byte[] P)                                                                            
            {                                                                                                                               
            int keylen = K.Length;                                                                                                          
            if ((keylen != 32) && (keylen != 64) && (keylen != 128))                                                                        
                throw new ArgumentException("Key bitLength not 32, 64, or 128 bytes.");                                                     
            int textlen = P.Length;                                                                                                         
            if ((textlen != 32) && (textlen != 64) && (textlen != 128))                                                                     
                throw new ArgumentException("Plaintext bitLength not 32, 64, or 128 bytes.");                                               
            if (keylen != textlen)                                                                                                          
                throw new ArgumentException("Plaintext bitLength must match key bitLength.");                                               
            if (T.Length != 16)                                                                                                             
                throw new ArgumentException("Tweak must be 16 bytes");                                                                      
                                                                                                                                            
            ulong [] P1 = SkeinByteConverter.BytesToWords(P);                                                                               
            EncryptBlock(SkeinByteConverter.BytesToWords(K), SkeinByteConverter.BytesToWords(T), P1, 0);                                    
            return SkeinByteConverter.WordsToBytes(P1);                                                                                     
            }                                                                                                                               
                                                                                                                                            
        /// <summary>                                                                                                                       
        /// Decrypt a block.                                                                                                                
        /// Requires key, ciphertext, and a tweak value.                                                                                    
        /// Returns decrypted text.                                                                                                         
        /// </summary>                                                                                                                      
        /// <param name="K">Key to decrypt. Must be 32, 64, or 128 bytes and same size as ciphertext C.</param>                             
        /// <param name="T">16 byte tweak value. Must be the same for encryption.</param>                                                   
        /// <param name="C">Ciphertext to decrypt. Must be 32, 64, or 128 bytes and same size as key K.</param>                             
        /// <returns>The decrypted bytes.</returns>                                                                                         
        public byte[] DecryptBlock(byte[] K, byte[] T, byte[] C)                                                                            
            {                                                                                                                               
            int keylen = K.Length;                                                                                                          
            if ((keylen != 32) && (keylen != 64) && (keylen != 128))                                                                        
                throw new ArgumentException("Key bitLength not 32, 64, or 128 bytes.");                                                     
            int cipherlen = C.Length;                                                                                                       
            if ((cipherlen != 32) && (cipherlen != 64) && (cipherlen != 128))                                                               
                throw new ArgumentException("Ciphertext bitLength not 32, 64, or 128 bytes.");                                              
            if (keylen != cipherlen)                                                                                                        
                throw new ArgumentException("Ciphertext bitLength must match key bitLength.");                                              
            if (T.Length != 16)                                                                                                             
                throw new ArgumentException("Tweak must be 16 bytes");                                                                      
                                                                                                                                            
            ulong [] C1 = SkeinByteConverter.BytesToWords(C);                                                                               
            DecryptBlock(SkeinByteConverter.BytesToWords(K), SkeinByteConverter.BytesToWords(T), C1, 0);                                    
            return SkeinByteConverter.WordsToBytes(C1);                                                                                     
            }                                                                                                                               
                                                                                                                                            
                                                                                                                                            
        #region Implementation                                                                                                              
        /// <summary>                                                                                                                       
        /// NUmber of 64 bit words per block Nw, and number of rounds Nr                                                                    
        /// </summary>                                                                                                                      
        uint Nw, Nr;                                                                                                                        
        /// <summary>                                                                                                                       
        /// The lengthened key state                                                                                                        
        /// </summary>                                                                                                                      
        ulong[] Ks = null;                                                                                                                  
        /// <summary>                                                                                                                       
        /// The lenghtened tweak values                                                                                                     
        /// </summary>                                                                                                                      
        ulong[] t = null;                                                                                                                   
                                                                                                                                            
                                                                                                                                            
        /// <summary>                                                                                                                       
        /// Encrypt a block in place. Thus P is the plaintext on input, and the cipertext on output                                         
        /// </summary>                                                                                                                      
        /// <param name="K">Key is 4, 8, or 16 64-bit values</param>                                                                        
        /// <param name="T">Two 64-bit values for the tweak value.</param>                                                                  
        /// <param name="P">Plaintext. A block of data, same bitLength as key K</param>                                                     
        /// <param name="start">Start offset into P for encryption</param>                                                                  
        protected void EncryptBlock(ulong[] K, ulong[] T, ulong[] P, ulong start)                                                           
            {                                                                                                                               
            if ((K.Length != 4) && (K.Length != 8) && (K.Length != 16))                                                                     
                throw new ArgumentOutOfRangeException("K""K must be 4,8, or 16 values in bitLength");                                     
            if (K.Length > P.Length - (int)start)                                                                                           
                throw new ArgumentOutOfRangeException("K and block""Block must have at least the bitLength of the key K");                
                                                                                                                                            
            // copy key to longer key for key scheduling                                                                                    
            Ks[Nw] = 0x5555555555555555UL; // Floor[2^64 / 3]                                                                               
            for (int i = 0; i < Nw; ++i)                                                                                                    
                {                                                                                                                           
                Ks[i] = K[i];                                                                                                               
                Ks[Nw] ^= K[i];                                                                                                             
                }                                                                                                                           
                                                                                                                                            
            // more key schedule parameters                                                                                                 
            t[0] = T[0];                                                                                                                    
            t[1] = T[1];                                                                                                                    
            t[2] = T[0] ^ T[1];                                                                                                             
                                                                                                                                            
            for (uint d = 0; d < Nr; ++d) // d is round number as in spec                                                                   
                {                                                                                                                           
                if ((d & 3) == 0)                                                                                                           
                    AddSubkey(d / 4, P, start);                                                                                             
                // apply MIX to data two words at a time                                                                                    
                for (uint j = 0; j < Nw / 2; ++j) // i is word number as in spec                                                            
                    {                                                                                                                       
                    ulong y0,y1;                                                                                                            
                    MIX(out y0, out y1, P[2 * j + start], P[2 * j + 1 + start], R[d & 7, j]);                                               
                    P[2 * j + start] = y0;                                                                                                  
                    P[2 * j + 1 + start] = y1;                                                                                              
                    }                                                                                                                       
                Permute(P, start);                                                                                                          
                }                                                                                                                           
                                                                                                                                            
            // final cipertext                                                                                                              
            AddSubkey(Nr / 4, P, start);                                                                                                    
            } // EncryptBlock                                                                                                               
                                                                                                                                            
        /// <summary>                                                                                                                       
        /// Decrypt a block in place. Thus C is the ciphertext on input, and the plaintext on output                                        
        /// </summary>                                                                                                                      
        /// <param name="K">Key, 4,8, or 16 64-bit values</param>                                                                           
        /// <param name="T">2 64-bit values</param>                                                                                         
        /// <param name="C">Ciphertext. A block of data, same bitLength as key K</param>                                                    
        protected void DecryptBlock(ulong[] K, ulong[] T, ulong[] C, ulong start)                                                           
            {                                                                                                                               
            if ((K.Length != 4) && (K.Length != 8) && (K.Length != 16))                                                                     
                throw new ArgumentOutOfRangeException("K""K must be 4,8, or 16 values in bitLength");                                     
            if (K.Length < C.Length - (int)start)                                                                                           
                throw new ArgumentOutOfRangeException("K and block""Block must have at least the bitLength of the key K");                
                                                                                                                                            
            // copy key to longer key                                                                                                       
            Ks[Nw] = 0x5555555555555555UL; // = 6148914691236517205UL; // Floor[2^64 / 3]                                                   
            for (int i = 0; i < Nw; ++i)                                                                                                    
                {                                                                                                                           
                Ks[i] = K[i];                                                                                                               
                Ks[Nw] ^= K[i];                                                                                                             
                }                                                                                                                           
                                                                                                                                            
            // key schedule                                                                                                                 
            t[0] = T[0];                                                                                                                    
            t[1] = T[1];                                                                                                                    
            t[2] = T[0] ^ T[1];                                                                                                             
                                                                                                                                            
            // initial key                                                                                                                  
            SubtractSubkey(Nr / 4, C, start);                                                                                               
                                                                                                                                            
            for (long d = (Nr - 1); d >= 0; --d) // d is round number as in spec                                                            
                {                                                                                                                           
                Unpermute(C, start);                                                                                                        
                                                                                                                                            
                // apply MIX to data                                                                                                        
                for (int j = 0; j < Nw / 2; ++j) // i is word number as in spec                                                             
                    {                                                                                                                       
                    ulong y0, y1;                                                                                                           
                    UnMIX(out y0, out y1, C[2 * j], C[2 * j + 1], R[d & 7, j]);                                                             
                    C[2 * j] = y0;                                                                                                          
                    C[2 * j + 1] = y1;                                                                                                      
                    }                                                                                                                       
                if ((d & 3) == 0)                                                                                                           
                    SubtractSubkey((ulong)(d / 4), C, start);                                                                               
                }                                                                                                                           
            } // DecryptBlock                                                                                                               
                                                                                                                                            
                                                                                                                                            
        /// <summary>                                                                                                                       
        /// Table 4, R[d,j] for Nw = 4,8,16                                                                                                 
        /// These tables give the rotate word amounts for                                                                                   
        /// each round and word                                                                                                             
        /// </summary>                                                                                                                      
        int[,] R = null;                                                                                                                    
        static int[,] Rtbl4 = {                                                                                                             
                { 5,56},                                                                                                                    
                {36,28},                                                                                                                    
                {13,46},                                                                                                                    
                {58,44},                                                                                                                    
                {26,20},                                                                                                                    
                {53,35},                                                                                                                    
                {11,42},                                                                                                                    
                {59,50}};                                                                                                                   
        static int[,] Rtbl8 = {                                                                                                             
                {38,30,50,53},                                                                                                              
                {48,20,43,31},                                                                                                              
                {34,14,15,27},                                                                                                              
                {26,12,58,7},                                                                                                               
                {33,49,8,42},                                                                                                               
                {39,27,41,14},                                                                                                              
                {29,26,11,9},                                                                                                               
                {33,51,39,35}};                                                                                                             
        static int[,] Rtbl16 = {                                                                                                            
                {55,43,37,40,16,22,38,12},                                                                                                  
                {25,25,46,13,14,13,52,57},                                                                                                  
                {33, 8,18,57,21,12,32,54},                                                                                                  
                {34,43,25,60,44, 9,59,34},                                                                                                  
                {28, 7,47,48,51, 9,35,41},                                                                                                  
                {17, 6,18,25,43,42,40,15},                                                                                                  
                {58, 7,32,45,19,18, 2,56},                                                                                                  
                {47,49,27,58,37,48,53,56}};                                                                                                 
                                                                                                                                            
        /// <summary>                                                                                                                       
        /// The MIX function from the spec                                                                                                  
        /// </summary>                                                                                                                      
        /// <param name="y0"></param>                                                                                                       
        /// <param name="y1"></param>                                                                                                       
        /// <param name="x0"></param>                                                                                                       
        /// <param name="x1"></param>                                                                                                       
        /// <param name="Rdj"></param>                                                                                                      
        void MIX(out ulong y0, out ulong y1, ulong x0, ulong x1, int Rdj)                                                                   
            {                                                                                                                               
            y0 = x0 + x1; // wraps mod 2^64                                                                                                 
            y1 = (x1 << Rdj) | (x1 >> (64 - Rdj));                                                                                          
            Debug.Assert(x1 == ((y1 >> Rdj) | (y1 << (64 - Rdj))), "Rotation failed");                                                      
            y1 ^= y0;                                                                                                                       
            }                                                                                                                               
                                                                                                                                            
        /// <summary>                                                                                                                       
        /// The reverse of the mix function from the spec                                                                                   
        /// </summary>                                                                                                                      
        /// <param name="x0"></param>                                                                                                       
        /// <param name="x1"></param>                                                                                                       
        /// <param name="y0"></param>                                                                                                       
        /// <param name="y1"></param>                                                                                                       
        /// <param name="Rdj"></param>                                                                                                      
        void UnMIX(out ulong x0, out ulong x1, ulong y0, ulong y1, int Rdj)                                                                 
            {                                                                                                                               
            y1 ^= y0;                                                                                                                       
            x1 = (y1 >> Rdj) | (y1 << (64 - Rdj));                                                                                          
            x0 = y0 - x1;                                                                                                                   
            }                                                                                                                               
                                                                                                                                            
                                                                                                                                            
        /// <summary>                                                                                                                       
        /// Permutation table 3, implemented as functions to call                                                                           
        /// </summary>                                                                                                                      
        delegate void PermuteDelegate(ulong[] P, ulong start);                                                                              
        PermuteDelegate Permute = null;                                                                                                     
        PermuteDelegate Unpermute = null;                                                                                                   
                                                                                                                                            
        static void Permute4(ulong[] P, ulong start)                                                                                        
            {                                                                                                                               
            // permutation                                                                                                                  
            // 0 1 2 3                                                                                                                      
            // 0 3 2 1                                                                                                                      
            // swap C[1] and C[3]                                                                                                           
            P[3 + start] ^= P[1 + start];                                                                                                   
            P[1 + start] ^= P[3 + start];                                                                                                   
            P[3 + start] ^= P[1 + start];                                                                                                   
            }                                                                                                                               
                                                                                                                                            
        static void Unpermute4(ulong[] P, ulong start)                                                                                      
            { // todo - same as permute4, merge?                                                                                            
            // permutation                                                                                                                  
            // 0 1 2 3                                                                                                                      
            // 0 3 2 1                                                                                                                      
            // swap C[1] and C[3]                                                                                                           
            P[3 + start] ^= P[1 + start];                                                                                                   
            P[1 + start] ^= P[3 + start];                                                                                                   
            P[3 + start] ^= P[1 + start];                                                                                                   
            }                                                                                                                               
                                                                                                                                            
        static void Unpermute8(ulong[] P, ulong start)                                                                                      
            {                                                                                                                               
            // Perform reverse permutation                                                                                                  
            //  0 1 2 3 4 5 6 7                                                                                                             
            //  2 1 4 7 6 5 0 3                                                                                                             
            // 0<-2<-4<-6<-0                                                                                                                
            // 3<->7                                                                                                                        
            ulong v = P[0 + start];                                                                                                         
            P[0 + start] = P[6 + start];                                                                                                    
            P[6 + start] = P[4 + start];                                                                                                    
            P[4 + start] = P[2 + start];                                                                                                    
            P[2 + start] = v;                                                                                                               
            P[3 + start] ^= P[7 + start];                                                                                                   
            P[7 + start] ^= P[3 + start];                                                                                                   
            P[3 + start] ^= P[7 + start];                                                                                                   
            }                                                                                                                               
                                                                                                                                            
        static void Permute8(ulong[] P, ulong start)                                                                                        
            {                                                                                                                               
            // Perform permutation                                                                                                          
            //  0 1 2 3 4 5 6 7                                                                                                             
            //  2 1 4 7 6 5 0 3                                                                                                             
            // 0<-2<-4<-6<-0                                                                                                                
            // 3<->7                                                                                                                        
            ulong v = P[0 + start];                                                                                                         
            P[0 + start] = P[2 + start];                                                                                                    
            P[2 + start] = P[4 + start];                                                                                                    
            P[4 + start] = P[6 + start];                                                                                                    
            P[6 + start] = v;                                                                                                               
            P[3 + start] ^= P[7 + start];                                                                                                   
            P[7 + start] ^= P[3 + start];                                                                                                   
            P[3 + start] ^= P[7 + start];                                                                                                   
            }                                                                                                                               
                                                                                                                                            
        static void Permute16(ulong[] P, ulong start)                                                                                       
            {                                                                                                                               
            // 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15                                                                               
            // 0  9  2 13  6 11  4 15 10  7 12  3 14  5  8  1                                                                               
            // 1<-9<-7<-15<-1                                                                                                               
            // 3<-13<-5<-11<-3                                                                                                              
            // 4<->6                                                                                                                        
            // 8<-10<-12<-14<-8                                                                                                             
            ulong v;                                                                                                                        
            v = P[1 + start]; P[1 + start] = P[9 + start]; P[9 + start] = P[7 + start]; P[7 + start] = P[15 + start]; P[15 + start] = v;    
            v = P[3 + start]; P[3 + start] = P[13 + start]; P[13 + start] = P[5 + start]; P[5 + start] = P[11 + start]; P[11 + start] = v;  
            P[4 + start] ^= P[6 + start]; P[6 + start] ^= P[4 + start]; P[4 + start] ^= P[6 + start];                                       
            v = P[8 + start]; P[8 + start] = P[10 + start]; P[10 + start] = P[12 + start]; P[12 + start] = P[14 + start]; P[14 + start] = v;
            }                                                                                                                               
                                                                                                                                            
        static void Unpermute16(ulong[] P, ulong start)                                                                                     
            {                                                                                                                               
            // 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15                                                                               
            // 0  9  2 13  6 11  4 15 10  7 12  3 14  5  8  1                                                                               
            // 1<-9<-7<-15<-1                                                                                                               
            // 3<-13<-5<-11<-3                                                                                                              
            // 4<->6                                                                                                                        
            // 8<-10<-12<-14<-8                                                                                                             
            ulong v;                                                                                                                        
            v = P[1 + start]; P[1 + start] = P[15 + start]; P[15 + start] = P[7 + start]; P[7 + start] = P[9 + start]; P[9 + start] = v;    
            v = P[3 + start]; P[3 + start] = P[11 + start]; P[11 + start] = P[5 + start]; P[5 + start] = P[13 + start]; P[13 + start] = v;  
            P[4 + start] ^= P[6 + start]; P[6 + start] ^= P[4 + start]; P[4 + start] ^= P[6 + start];                                       
            v = P[8 + start]; P[8 + start] = P[14 + start]; P[14 + start] = P[12 + start]; P[12 + start] = P[10 + start]; P[10 + start] = v;
            }                                                                                                                               
                                                                                                                                            
        void AddSubkey(ulong s, ulong[] P, ulong start)                                                                                     
            { // todo - note this is merely a rotation of Ks and ti, perhaps implement them that way?                                       
            uint top = Nw + 1;                                                                                                              
            for (ulong i = 0; i <= Nw - 4; ++i)                                                                                             
                P[i + start] += Ks[(s + i) % top];                                                                                          
            P[Nw - 3 + start] += Ks[(s + Nw - 3) % top] + t[s % 3];                                                                         
            P[Nw - 2 + start] += Ks[(s + Nw - 2) % top] + t[(s + 1) % 3];                                                                   
            P[Nw - 1 + start] += Ks[(s + Nw - 1) % top] + s;                                                                                
            }                                                                                                                               
                                                                                                                                            
        void SubtractSubkey(ulong s, ulong[] P, ulong start)                                                                                
            { // todo - note this is merely a rotation of Ks and ti, perhaps implement them that way?                                       
            uint top = Nw + 1;                                                                                                              
            for (ulong i = 0; i <= Nw - 4; ++i)                                                                                             
                P[i + start] -= Ks[(s + i) % top];                                                                                          
            P[Nw - 3 + start] -= Ks[(s + Nw - 3) % top] + t[s % 3];                                                                         
            P[Nw - 2 + start] -= Ks[(s + Nw - 2) % top] + t[(s + 1) % 3];                                                                   
            P[Nw - 1 + start] -= Ks[(s + Nw - 1) % top] + s;                                                                                
            }                                                                                                                               
        #endregion                                                                                                                          
                                                                                                                                            
        } // Threefish                                                                                                                      
                                                                                                                                            
    #region Helper                                                                                                                          
    /// <summary>                                                                                                                           
    /// This class provides methods to convert between                                                                                      
    /// ulong[]  and byte [] using the byte ordering of Skein                                                                               
    /// </summary>                                                                                                                          
    class SkeinByteConverter                                                                                                                
        {                                                                                                                                   
        /// <summary>                                                                                                                       
        /// Given bytes b0,b1,b2,...,bn, return the corresponding                                                                           
        /// 64 bit ulong using at most the first 8 bytes.                                                                                   
        /// </summary>                                                                                                                      
        /// <param name="list">A list of bytes to convert to ulongs </param>                                                                
        /// <param name="start">The start index in the array</param>                                                                        
        /// <returns></returns>                                                                                                             
        static public ulong ToInt(byte[] list, int start)                                                                                   
            {                                                                                                                               
            int shift = 0;                                                                                                                  
            ulong val = 0, temp;                                                                                                            
            for (int i = 0; i < 8; ++i)                                                                                                     
                {                                                                                                                           
                if (start + i < list.Length)                                                                                                
                    temp = list[start + i];                                                                                                 
                else                                                                                                                        
                    temp = 0;                                                                                                               
                temp <<= shift;                                                                                                             
                val |= temp;                                                                                                                
                shift += 8;                                                                                                                 
                }                                                                                                                           
            return val;                                                                                                                     
            }                                                                                                                               
        /// <summary>                                                                                                                       
        /// Convert a value v into n bytes                                                                                                  
        /// </summary>                                                                                                                      
        /// <param name="v">A 64 bit value</param>                                                                                          
        /// <param name="n">The number of bytes to parse</param>                                                                            
        /// <returns>An array of converted bytes</returns>                                                                                  
        static public byte[] ToBytes(ulong v, int n)                                                                                        
            {                                                                                                                               
            Trace.Assert(n <= 8);  // otherwise we need to redesign this function                                                           
            List<byte> b = new List<byte>();                                                                                                
            for (int i = 0; i < n; ++i)                                                                                                     
                b.Add((byte)(v >> (8 * i)));                                                                                                
            return b.ToArray();                                                                                                             
            }                                                                                                                               
                                                                                                                                            
        /// <summary>                                                                                                                       
        /// Convert an array of 8n bytes to n ulongs                                                                                        
        /// </summary>                                                                                                                      
        /// <param name="arr"></param>                                                                                                      
        /// <returns>An array of converted longs</returns>                                                                                  
        static public ulong[] BytesToWords(byte[] arr)                                                                                      
            {                                                                                                                               
            int n = arr.Length;                                                                                                             
            ulong [] w = new ulong[(n + 7) / 8];                                                                                            
            for (int i = 0; i < n; i += 8)                                                                                                  
                w[i / 8] = ToInt(arr, i);                                                                                                   
            return w;                                                                                                                       
            }                                                                                                                               
        /// <summary>                                                                                                                       
        /// Convert n words to 8n bytes                                                                                                     
        /// </summary>                                                                                                                      
        /// <param name="w">An arrya of ulongs to convert </param>                                                                          
        /// <returns>The array of bytes</returns>                                                                                           
        static public byte[] WordsToBytes(ulong[] w)                                                                                        
            {                                                                                                                               
            byte [] v = new byte[w.Length * 8];                                                                                             
            for (int i = 0; i < w.Length; ++i)                                                                                              
                ToBytes(w[i], 8).CopyTo(v, i * 8);                                                                                          
            return v;                                                                                                                       
            }                                                                                                                               
                                                                                                                                            
        } // SkeinByteConverter                                                                                                             
    #endregion                                                                                                                              
                                                                                                                                            
    } // namespace Crypto                                                                                                                   
    } // namespace Lomont