/* LibTomCrypt, modular cryptographic library -- Tom St Denis */
/* SPDX-License-Identifier: Unlicense */

/**
  @file tea.c
  Implementation of TEA, Steffen Jaeckel
*/
#include "tomcrypt_private.h"

#ifdef LTC_TEA

const struct ltc_cipher_descriptor tea_desc =
{
    "tea",
    26,
    16, 16, 8, 32,
    &tea_setup,
    &tea_ecb_encrypt,
    &tea_ecb_decrypt,
    &tea_test,
    &tea_done,
    &tea_keysize,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
};

#define DELTA 0x9E3779B9uL
#define SUM 0xC6EF3720uL

int tea_setup(const unsigned char *key, int keylen, int num_rounds, symmetric_key *skey)
{
   LTC_ARGCHK(key != NULL);
   LTC_ARGCHK(skey != NULL);

   /* check arguments */
   if (keylen != 16) {
      return CRYPT_INVALID_KEYSIZE;
   }

   if (num_rounds != 0 && num_rounds != 32) {
      return CRYPT_INVALID_ROUNDS;
   }

   /* load key */
   LOAD32H(skey->tea.k[0], key+0);
   LOAD32H(skey->tea.k[1], key+4);
   LOAD32H(skey->tea.k[2], key+8);
   LOAD32H(skey->tea.k[3], key+12);

   return CRYPT_OK;
}

/**
  Encrypts a block of text with TEA
  @param pt The input plaintext (8 bytes)
  @param ct The output ciphertext (8 bytes)
  @param skey The key as scheduled
  @return CRYPT_OK if successful
*/
int tea_ecb_encrypt(const unsigned char *pt, unsigned char *ct, const symmetric_key *skey)
{
   ulong32 y, z, sum = 0;
   const ulong32 delta = DELTA;
   int r;

   LTC_ARGCHK(pt   != NULL);
   LTC_ARGCHK(ct   != NULL);
   LTC_ARGCHK(skey != NULL);

   LOAD32H(y, &pt[0]);
   LOAD32H(z, &pt[4]);
   for (r = 0; r < 32; r++) {
      sum += delta;
      y += ((z<<4) + skey->tea.k[0]) ^ (z + sum) ^ ((z>>5) + skey->tea.k[1]);
      z += ((y<<4) + skey->tea.k[2]) ^ (y + sum) ^ ((y>>5) + skey->tea.k[3]);
   }
   STORE32H(y, &ct[0]);
   STORE32H(z, &ct[4]);
   return CRYPT_OK;
}

/**
  Decrypts a block of text with TEA
  @param ct The input ciphertext (8 bytes)
  @param pt The output plaintext (8 bytes)
  @param skey The key as scheduled
  @return CRYPT_OK if successful
*/
int tea_ecb_decrypt(const unsigned char *ct, unsigned char *pt, const symmetric_key *skey)
{
   ulong32 v0, v1, sum = SUM;
   const ulong32 delta = DELTA;
   int r;

   LTC_ARGCHK(pt != NULL);
   LTC_ARGCHK(ct != NULL);
   LTC_ARGCHK(skey != NULL);

   LOAD32H(v0, &ct[0]);
   LOAD32H(v1, &ct[4]);

   for (r = 0; r < 32; r++) {
      v1 -= ((v0 << 4) + skey->tea.k[2]) ^ (v0 + sum) ^ ((v0 >> 5) + skey->tea.k[3]);
      v0 -= ((v1 << 4) + skey->tea.k[0]) ^ (v1 + sum) ^ ((v1 >> 5) + skey->tea.k[1]);
      sum -= delta;
   }

   STORE32H(v0, &pt[0]);
   STORE32H(v1, &pt[4]);
   return CRYPT_OK;
}

/**
  Performs a self-test of the TEA block cipher
  @return CRYPT_OK if functional, CRYPT_NOP if self-test has been disabled
*/
int tea_test(void)
{
 #ifndef LTC_TEST
    return CRYPT_NOP;
 #else
    static const struct {
        const char *key, *pt, *ct;
    } tests[] = {
       {
         "00000000000000000000000000000000",
         "0000000000000000",
         "41ea3a0a94baa940"
       }, {
         "32a1e65408b63bb9214105744ec5d2e2",
         "5ada1d89a9c3801a",
         "dd46249e28aa0b4b"
       }, {
         "60388adadf70a1f5d9cb4e097d2c6c57",
         "7a6adb4d69c53e0f",
         "44b71215cf25368a"
       }, {
         "4368d2249bd0321eb7c56d5b63a1bfac",
         "5a5d7ca2e186c41a",
         "91f56dff7281794f"
       }, {
         "5c60bff27072d01c4513c5eb8f3a38ab",
         "80d9c4adcf899635",
         "2bb0f1b3c023ed11"
       }
    };
   unsigned char ptct[2][8];
   unsigned char tmp[2][8];
   unsigned char key[16];
   unsigned long l;
   symmetric_key skey;
   size_t i;
   int err, y;
   for (i = 0; i < LTC_ARRAY_SIZE(tests); i++) {
       zeromem(&skey, sizeof(skey));

       l = sizeof(key);
       if ((err = base16_decode(tests[i].key, XSTRLEN(tests[i].key), key, &l)) != CRYPT_OK) return err;
       l = sizeof(ptct[0]);
       if ((err = base16_decode(tests[i].pt, XSTRLEN(tests[i].pt), ptct[0], &l)) != CRYPT_OK) return err;
       l = sizeof(ptct[1]);
       if ((err = base16_decode(tests[i].ct, XSTRLEN(tests[i].ct), ptct[1], &l)) != CRYPT_OK) return err;

       if ((err = tea_setup(key, 16, 0, &skey)) != CRYPT_OK)  {
          return err;
       }
       tea_ecb_encrypt(ptct[0], tmp[0], &skey);
       tea_ecb_decrypt(tmp[0], tmp[1], &skey);

       if (ltc_compare_testvector(tmp[0], 8, ptct[1], 8, "TEA Encrypt", i) != 0 ||
             ltc_compare_testvector(tmp[1], 8, ptct[0], 8, "TEA Decrypt", i) != 0) {
          return CRYPT_FAIL_TESTVECTOR;
       }

      /* now see if we can encrypt all zero bytes 1000 times, decrypt and come back where we started */
      for (y = 0; y < 8; y++) tmp[0][y] = 0;
      for (y = 0; y < 1000; y++) tea_ecb_encrypt(tmp[0], tmp[0], &skey);
      for (y = 0; y < 1000; y++) tea_ecb_decrypt(tmp[0], tmp[0], &skey);
      for (y = 0; y < 8; y++) if (tmp[0][y] != 0) return CRYPT_FAIL_TESTVECTOR;
   } /* for */

   return CRYPT_OK;
 #endif
}

/** Terminate the context
   @param skey    The scheduled key
*/
void tea_done(symmetric_key *skey)
{
  LTC_UNUSED_PARAM(skey);
}

/**
  Gets suitable key size
  @param keysize [in/out] The length of the recommended key (in bytes).  This function will store the suitable size back in this variable.
  @return CRYPT_OK if the input key size is acceptable.
*/
int tea_keysize(int *keysize)
{
   LTC_ARGCHK(keysize != NULL);
   if (*keysize < 16) {
      return CRYPT_INVALID_KEYSIZE;
   }
   *keysize = 16;
   return CRYPT_OK;
}

#undef DELTA
#undef SUM

#endif

