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

/**
   @file ocb3_init.c
   OCB implementation, initialize state, by Tom St Denis
*/
#include "tomcrypt_private.h"

#ifdef LTC_OCB3_MODE

static void s_ocb3_int_calc_offset_zero(ocb3_state *ocb, const unsigned char *nonce, unsigned long noncelen, unsigned long taglen)
{
   int x, y, bottom;
   int idx, shift;
   unsigned char iNonce[MAXBLOCKSIZE];
   unsigned char iKtop[MAXBLOCKSIZE];
   unsigned char iStretch[MAXBLOCKSIZE+8];

   /* Nonce = zeros(127-bitlen(N)) || 1 || N          */
   zeromem(iNonce, sizeof(iNonce));
   for (x = ocb->block_len-1, y=0; y<(int)noncelen; x--, y++) {
     iNonce[x] = nonce[noncelen-y-1];
   }
   iNonce[x] = 0x01;
   iNonce[0] |= ((taglen*8) % 128) << 1;

   /* bottom = str2num(Nonce[123..128])               */
   bottom = iNonce[ocb->block_len-1] & 0x3F;

   /* Ktop = ENCIPHER(K, Nonce[1..122] || zeros(6))   */
   iNonce[ocb->block_len-1] = iNonce[ocb->block_len-1] & 0xC0;
   if ((ecb_encrypt_block(iNonce, iKtop, &ocb->key)) != CRYPT_OK) {
      zeromem(ocb->Offset_current, ocb->block_len);
      return;
   }

   /* Stretch = Ktop || (Ktop[1..64] xor Ktop[9..72]) */
   for (x = 0; x < ocb->block_len; x++) {
     iStretch[x] = iKtop[x];
   }
   for (y = 0; y < 8; y++) {
     iStretch[x+y] = iKtop[y] ^ iKtop[y+1];
   }

   /* Offset_0 = Stretch[1+bottom..128+bottom]        */
   idx = bottom / 8;
   shift = (bottom % 8);
   for (x = 0; x < ocb->block_len; x++) {
      ocb->Offset_current[x] = iStretch[idx+x] << shift;
      if (shift > 0) {
        ocb->Offset_current[x] |= iStretch[idx+x+1] >> (8-shift);
      }
   }
}

#define polys ocb3_polys
static const struct {
    int           len;
    unsigned char poly_mul[MAXBLOCKSIZE];
} polys[] = {
{
    8,
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B }
}, {
    16,
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87 }
}
};

/**
   Initialize an OCB context
   @param ocb       [out] The destination of the OCB state
   @param cipher    The index of the desired cipher
   @param key       The secret key
   @param keylen    The length of the secret key (octets)
   @param nonce     The session nonce
   @param noncelen  The length of the session nonce (octets, up to 15)
   @param taglen    The length of the tag (octets, up to 16)
   @return CRYPT_OK if successful
*/
int ocb3_init(ocb3_state *ocb, int cipher,
             const unsigned char *key, unsigned long keylen,
             const unsigned char *nonce, unsigned long noncelen,
             unsigned long taglen)
{
   int poly, x, y, m, err;
   unsigned char *previous, *current;

   LTC_ARGCHK(ocb   != NULL);
   LTC_ARGCHK(key   != NULL);
   LTC_ARGCHK(nonce != NULL);

   /* valid cipher? */
   if ((err = cipher_is_valid(cipher)) != CRYPT_OK) {
      return err;
   }

   /* Valid Nonce?
    * As of RFC7253: "string of no more than 120 bits" */
   if (noncelen > (120/8)) {
      return CRYPT_INVALID_ARG;
   }

   /* The blockcipher must have a 128-bit blocksize */
   if (cipher_descriptor[cipher].block_length != 16) {
      return CRYPT_INVALID_ARG;
   }

   /* The TAGLEN may be any value up to 128 (bits) */
   if (taglen > 16) {
      return CRYPT_INVALID_ARG;
   }
   ocb->tag_len = taglen;

   /* determine which polys to use */
   ocb->block_len = cipher_descriptor[cipher].block_length;
   x = (int)LTC_ARRAY_SIZE(polys);
   for (poly = 0; poly < x; poly++) {
       if (polys[poly].len == ocb->block_len) {
          break;
       }
   }
   if (poly == x) {
      return CRYPT_INVALID_ARG; /* block_len not found in polys */
   }
   if (polys[poly].len != ocb->block_len) {
      return CRYPT_INVALID_ARG;
   }

   /* schedule the key */
   if ((err = ecb_start(cipher, key, keylen, 0, &ocb->key)) != CRYPT_OK) {
      return err;
   }

   /* L_* = ENCIPHER(K, zeros(128)) */
   zeromem(ocb->L_star, ocb->block_len);
   if ((err = ecb_encrypt_block(ocb->L_star, ocb->L_star, &ocb->key)) != CRYPT_OK) {
      return err;
   }

   /* compute L_$, L_0, L_1, ... */
   for (x = -1; x < 32; x++) {
      if (x == -1) {                /* gonna compute: L_$ = double(L_*) */
         current  = ocb->L_dollar;
         previous = ocb->L_star;
      }
      else if (x == 0) {            /* gonna compute: L_0 = double(L_$) */
         current  = ocb->L_[0];
         previous = ocb->L_dollar;
      }
      else {                        /* gonna compute: L_i = double(L_{i-1}) for every integer i > 0 */
         current  = ocb->L_[x];
         previous = ocb->L_[x-1];
      }
      m = previous[0] >> 7;
      for (y = 0; y < ocb->block_len-1; y++) {
         current[y] = ((previous[y] << 1) | (previous[y+1] >> 7)) & 255;
      }
      current[ocb->block_len-1] = (previous[ocb->block_len-1] << 1) & 255;
      if (m == 1) {
         /* current[] = current[] XOR polys[poly].poly_mul[]*/
         ocb3_int_xor_blocks(current, current, polys[poly].poly_mul, ocb->block_len);
      }
   }

   /* initialize ocb->Offset_current = Offset_0 */
   s_ocb3_int_calc_offset_zero(ocb, nonce, noncelen, taglen);

   /* initialize checksum to all zeros */
   zeromem(ocb->checksum, ocb->block_len);

   /* set block index */
   ocb->block_index = 1;

   /* initialize AAD related stuff */
   ocb->ablock_index = 1;
   ocb->adata_buffer_bytes = 0;
   zeromem(ocb->aOffset_current, ocb->block_len);
   zeromem(ocb->aSum_current, ocb->block_len);

   return CRYPT_OK;
}

#undef polys

#endif
