-----------------------------------------------------------------------------
-- |
-- Module      :  Codec.Encryption.TEA
-- Copyright   :  (c) John Meacham 2008
-- License     :  BSD-style (see the file ReadMe.tex)
--
-- Maintainer  :  john@repetae.net (http://repetae.net/)
-- Stability   :  experimental
-- Portability :  portable
--
-- Implementation of the TEA tiny encryption algorithm
--
-----------------------------------------------------------------------------

module Codec.Encryption.TEA(
    TEAKey(TEAKey),
    encrypt,
    decrypt
    ) where

import Data.Bits
import Data.Word

-- We don't use LargeKey for both efficiency and practical reasons.

data TEAKey = TEAKey {-# UNPACK #-} !Word32 {-# UNPACK #-} !Word32 {-# UNPACK #-} !Word32 {-# UNPACK #-} !Word32


delta :: Word32
delta = 0x9e3779b9

rounds = 32

encrypt ::  TEAKey -> Word64 -> Word64
encrypt (TEAKey k0 k1 k2 k3) v = f rounds 0 v0 v1 where
    v0,v1 :: Word32
    v0 = fromIntegral v
    v1 = fromIntegral $ v `shiftR` 32
    f a b c d | a `seq` b `seq` c `seq` d `seq` False = undefined
    f 0 _   v0 v1 = (fromIntegral v1 `shiftL` 32) .|. (fromIntegral v0 .&. 0xffffffff)
    f n sum v0 v1 = f (n - 1) sum' v0' v1' where
        sum' = sum + delta
        v0' = (v0 + (((v1 `shiftL` 4) + k0) `xor` (v1 + sum') `xor` ((v1 `shiftR` 5) + k1)))
        v1' = (v1 + (((v0' `shiftL` 4) + k2) `xor` (v0' + sum') `xor` ((v0' `shiftR` 5) + k3)))


decrypt ::  TEAKey -> Word64 -> Word64
decrypt (TEAKey k0 k1 k2 k3) v = f rounds 0xC6EF3720 v0 v1 where
    v0,v1 :: Word32
    v0 = fromIntegral v
    v1 = fromIntegral $ v `shiftR` 32
    f a b c d | a `seq` b `seq` c `seq` d `seq` False = undefined
    f 0 _   v0 v1 = (fromIntegral v1 `shiftL` 32) .|. (fromIntegral v0  .&. 0xFFFFFFFF)
    f n sum v0 v1 = f (n - 1) (sum - delta) v0' v1' where
        v1' = (v1 - (((v0 `shiftL` 4) + k2) `xor` (v0 + sum) `xor` ((v0 `shiftR` 5) + k3)))
        v0' = (v0 - (((v1' `shiftL` 4) + k0) `xor` (v1' + sum) `xor` ((v1' `shiftR` 5) + k1)))



{-
 void encrypt (unsigned long* v, unsigned long* k) {
    unsigned long v0=v[0], v1=v[1], sum=0, i;           /* set up */
    unsigned long delta=0x9e3779b9;                     /* a key schedule constant */
    unsigned long k0=k[0], k1=k[1], k2=k[2], k3=k[3];   /* cache key */
    for (i=0; i < 32; i++) {                            /* basic cycle start */
        sum += delta;
        v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
        v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);  /* end cycle */
    }
    v[0]=v0; v[1]=v1;
}

void decrypt (unsigned long* v, unsigned long* k) {
    unsigned long v0=v[0], v1=v[1], sum=0xC6EF3720, i;  /* set up */
    unsigned long delta=0x9e3779b9;                     /* a key schedule constant */
    unsigned long k0=k[0], k1=k[1], k2=k[2], k3=k[3];   /* cache key */
    for (i=0; i<32; i++) {                              /* basic cycle start */
        v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
        v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
        sum -= delta;                                   /* end cycle */
    }
    v[0]=v0; v[1]=v1;
}

-}