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

/* Based on idea.cpp - originally written and placed in the public domain by Wei Dai
   https://github.com/weidai11/cryptopp/blob/master/idea.cpp

   Patents should be expired. On 2017-10-16 wikipedia says:
   https://en.wikipedia.org/wiki/International_Data_Encryption_Algorithm

   A patent application for IDEA was first filed in Switzerland (CH A 1690/90) on May 18, 1990,
   then an international patent application was filed under the Patent Cooperation Treaty on
   May 16, 1991. Patents were eventually granted in Austria, France, Germany, Italy, the Netherlands,
   Spain, Sweden, Switzerland, the United Kingdom, (European Patent Register entry for European
   patent no. 0482154, filed May 16, 1991, issued June 22, 1994 and expired May 16, 2011),
   the United States (U.S. Patent 5,214,703, issued May 25, 1993 and expired January 7, 2012)
   and Japan (JP 3225440) (expired May 16, 2011).
 */

#include "tomcrypt_private.h"

#ifdef LTC_IDEA

const struct ltc_cipher_descriptor idea_desc = {
   "idea",
   24,                  /* cipher_ID */
   16, 16, 8, 8,        /* min_key_len, max_key_len, block_len, default_rounds */
   &idea_setup,
   &idea_ecb_encrypt,
   &idea_ecb_decrypt,
   &idea_test,
   &idea_done,
   &idea_keysize,
   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
};

typedef unsigned short int ushort16;

#define LOW16(x)     ((x)&0xffff)  /* compiler should be able to optimize this away if x is 16 bits */
#define HIGH16(x)    ((x)>>16)
#define MUL(a,b)     {                                               \
                         ulong32 p = (ulong32)LOW16(a) * b;          \
                         if (p) {                                     \
                            p = LOW16(p) - HIGH16(p);               \
                            a = (ushort16)p - (ushort16)HIGH16(p);   \
                         }                                            \
                         else                                         \
                            a = 1 - a - b;                            \
                      }
#define STORE16(x,y) { (y)[0] = (unsigned char)(((x)>>8)&255); (y)[1] = (unsigned char)((x)&255); }
#define LOAD16(x,y)  { x = ((ushort16)((y)[0] & 255)<<8) | ((ushort16)((y)[1] & 255)); }

static ushort16 s_mul_inv(ushort16 x)
{
   ushort16 y = x;
   unsigned i;

   for (i = 0; i < 15; i++) {
      MUL(y, LOW16(y));
      MUL(y, x);
   }
   return LOW16(y);
}

static ushort16 s_add_inv(ushort16 x)
{
   return LOW16(0 - x);
}

static int s_setup_key(const unsigned char *key, symmetric_key *skey)
{
   int i, j;
   ushort16 *e_key = skey->idea.ek;
   ushort16 *d_key = skey->idea.dk;

   /* prepare enc key */
   for (i = 0; i < 8; i++) {
      LOAD16(e_key[i], key + 2 * i);
   }
   for (; i < LTC_IDEA_KEYLEN; i++) {
      j = (i - i % 8) - 8;
      e_key[i] = LOW16((e_key[j+(i+1)%8] << 9) | (e_key[j+(i+2)%8] >> 7));
   }

   /* prepare dec key */
   for (i = 0; i < LTC_IDEA_ROUNDS; i++) {
      d_key[i*6+0] = s_mul_inv(e_key[(LTC_IDEA_ROUNDS-i)*6+0]);
      d_key[i*6+1] = s_add_inv(e_key[(LTC_IDEA_ROUNDS-i)*6+1+(i>0 ? 1 : 0)]);
      d_key[i*6+2] = s_add_inv(e_key[(LTC_IDEA_ROUNDS-i)*6+2-(i>0 ? 1 : 0)]);
      d_key[i*6+3] = s_mul_inv(e_key[(LTC_IDEA_ROUNDS-i)*6+3]);
      d_key[i*6+4] =           e_key[(LTC_IDEA_ROUNDS-1-i)*6+4];
      d_key[i*6+5] =           e_key[(LTC_IDEA_ROUNDS-1-i)*6+5];
   }
   d_key[i*6+0] = s_mul_inv(e_key[(LTC_IDEA_ROUNDS-i)*6+0]);
   d_key[i*6+1] = s_add_inv(e_key[(LTC_IDEA_ROUNDS-i)*6+1]);
   d_key[i*6+2] = s_add_inv(e_key[(LTC_IDEA_ROUNDS-i)*6+2]);
   d_key[i*6+3] = s_mul_inv(e_key[(LTC_IDEA_ROUNDS-i)*6+3]);

   return CRYPT_OK;
}

static int s_process_block(const unsigned char *in, unsigned char *out, const ushort16 *m_key)
{
   int i;
   ushort16 x0, x1, x2, x3, t0, t1;

   LOAD16(x0, in + 0);
   LOAD16(x1, in + 2);
   LOAD16(x2, in + 4);
   LOAD16(x3, in + 6);

   for (i = 0; i < LTC_IDEA_ROUNDS; i++) {
      MUL(x0, m_key[i*6+0]);
      x1 += m_key[i*6+1];
      x2 += m_key[i*6+2];
      MUL(x3, m_key[i*6+3]);
      t0 = x0^x2;
      MUL(t0, m_key[i*6+4]);
      t1 = t0 + (x1^x3);
      MUL(t1, m_key[i*6+5]);
      t0 += t1;
      x0 ^= t1;
      x3 ^= t0;
      t0 ^= x1;
      x1 = x2^t1;
      x2 = t0;
   }

   MUL(x0, m_key[LTC_IDEA_ROUNDS*6+0]);
   x2 += m_key[LTC_IDEA_ROUNDS*6+1];
   x1 += m_key[LTC_IDEA_ROUNDS*6+2];
   MUL(x3, m_key[LTC_IDEA_ROUNDS*6+3]);

   STORE16(x0, out + 0);
   STORE16(x2, out + 2);
   STORE16(x1, out + 4);
   STORE16(x3, out + 6);

   return CRYPT_OK;
}

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

   if (num_rounds != 0 && num_rounds != 8) return CRYPT_INVALID_ROUNDS;
   if (keylen != 16) return CRYPT_INVALID_KEYSIZE;

   return s_setup_key(key, skey);
}

int idea_ecb_encrypt(const unsigned char *pt, unsigned char *ct, const symmetric_key *skey)
{
   int err = s_process_block(pt, ct, skey->idea.ek);
#ifdef LTC_CLEAN_STACK
   burn_stack(sizeof(ushort16) * 6 + sizeof(int));
#endif
   return err;
}

int idea_ecb_decrypt(const unsigned char *ct, unsigned char *pt, const symmetric_key *skey)
{
   int err = s_process_block(ct, pt, skey->idea.dk);
#ifdef LTC_CLEAN_STACK
   burn_stack(sizeof(ushort16) * 6 + sizeof(int));
#endif
   return err;
}

void idea_done(symmetric_key *skey)
{
   LTC_UNUSED_PARAM(skey);
}

int idea_keysize(int *keysize)
{
   LTC_ARGCHK(keysize != NULL);
   if (*keysize < 16) {
      return CRYPT_INVALID_KEYSIZE;
   }
   *keysize = 16;
   return CRYPT_OK;
}

int idea_test(void)
{
#ifndef LTC_TEST
   return CRYPT_NOP;
#else
   static const struct {
      unsigned char key[16], pt[8], ct[8];
   } tests[] = {
      {
         /* key */ { 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
         /* pt  */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
         /* ct  */ { 0xB1, 0xF5, 0xF7, 0xF8, 0x79, 0x01, 0x37, 0x0F }
      },
      {
         /* key */ { 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
         /* pt  */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
         /* ct  */ { 0xB3, 0x92, 0x7D, 0xFF, 0xB6, 0x35, 0x86, 0x26 }
      },
      {
         /* key */ { 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
         /* pt  */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
         /* ct  */ { 0xE9, 0x87, 0xE0, 0x02, 0x9F, 0xB9, 0x97, 0x85 }
      },
      {
         /* key */ { 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
         /* pt  */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
         /* ct  */ { 0x75, 0x4A, 0x03, 0xCE, 0x08, 0xDB, 0x7D, 0xAA }
      },
      {
         /* key */ { 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
         /* pt  */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
         /* ct  */ { 0xF0, 0x15, 0xF9, 0xFB, 0x0C, 0xFC, 0x7E, 0x1C }
      },
   };

   unsigned char buf[2][8];
   symmetric_key key;
   int err, x;

   if (sizeof(ushort16) != 2) {
      return CRYPT_FAIL_TESTVECTOR;
   }

   for (x = 0; x < (int)(sizeof(tests)/sizeof(tests[0])); x++) {
      if ((err = idea_setup(tests[x].key, 16, 8, &key)) != CRYPT_OK) {
         return err;
      }
      if ((err = idea_ecb_encrypt(tests[x].pt, buf[0], &key)) != CRYPT_OK) {
         return err;
      }
      if (compare_testvector(buf[0], 8, tests[x].ct, 8, "IDEA Encrypt", x)) {
         return CRYPT_FAIL_TESTVECTOR;
      }
      if ((err = idea_ecb_decrypt(tests[x].ct, buf[1], &key)) != CRYPT_OK) {
         return err;
      }
      if (compare_testvector(buf[1], 8, tests[x].pt, 8, "IDEA Decrypt", x)) {
         return CRYPT_FAIL_TESTVECTOR;
      }
   }

   return CRYPT_OK;
#endif
}

#endif
