/*
 * cifra - embedded cryptography library
 * Written in 2014 by Joseph Birr-Pixton <jpixton@gmail.com>
 *
 * To the extent possible under law, the author(s) have dedicated all
 * copyright and related and neighboring rights to this software to the
 * public domain worldwide. This software is distributed without any
 * warranty.
 *
 * You should have received a copy of the CC0 Public Domain Dedication
 * along with this software. If not, see
 * <http://creativecommons.org/publicdomain/zero/1.0/>.
 */


#include "norx.h"
#include "bitops.h"
#include "handy.h"
#include "blockwise.h"
#include "tassert.h"

#include <string.h>

typedef struct
{
  uint32_t s[16];
} norx32_ctx;

/* Domain separation constants */
#define DOMAIN_HEADER   0x01
#define DOMAIN_PAYLOAD  0x02
#define DOMAIN_TRAILER  0x04
#define DOMAIN_TAG      0x08

#define WORD_BYTES 4
#define WORD_BITS 32
#define ROUNDS 4
#define DEGREE 1
#define TAG_BITS 128
#define RATE_BYTES 48
#define RATE_WORDS 12

static void permute(norx32_ctx *restrict ctx)
{
#ifdef CORTEX_M0
  /* Register usage: A-D r2, r3, r4, r5.
   * Temps: r6 */

#define in(xx) "%[" #xx "]"

  /* Load numbered slots of S into r2-r5 */
#define LOAD(u, v, w, x)           \
  "  ldr r2, [%[S], " in(u) "]\n"  \
  "  ldr r3, [%[S], " in(v) "]\n"  \
  "  ldr r4, [%[S], " in(w) "]\n"  \
  "  ldr r5, [%[S], " in(x) "]\n"

  /* Store r2-r5 into numbered slots of S */
#define STORE(u, v, w, x)          \
  "  str r2, [%[S], " in(u) "]\n"  \
  "  str r3, [%[S], " in(v) "]\n"  \
  "  str r4, [%[S], " in(w) "]\n"  \
  "  str r5, [%[S], " in(x) "]\n"

  /* This is H() plus the xor and rotate in one step of G.
   * rx is the register containing x (read/write)
   * ry is the register containing y (read)
   * rw is the register containing d (read/write)
   * rot is the rotation constant r_n */
#define P(rx, ry, rw, rot)      \
  "  mov r6, " #rx "\n"         \
  "  and " #rx ", " #ry "\n"    \
  "  lsl " #rx ", #1\n"         \
  "  eor " #rx ", r6\n"         \
  "  eor " #rx ", " #ry "\n"    \
  "  mov r6, #" #rot "\n"       \
  "  eor " #rw ", " #rx "\n"    \
  "  ror " #rw ", r6\n"

  /* The function G.  s is the state array, a-d are indices
   * into it. */
#define G(s, a, b, c, d)      \
  __asm__ (                   \
            LOAD(A, B, C, D)  \
            P(r2, r3, r5, 8)  \
            P(r4, r5, r3, 11) \
            P(r2, r3, r5, 16) \
            P(r4, r5, r3, 31) \
            STORE(A, B, C, D) \
          :                   \
          : [S] "r" (s),      \
            [A] "i" (a << 2), \
            [B] "i" (b << 2), \
            [C] "i" (c << 2), \
            [D] "i" (d << 2)  \
          : "memory", "cc", "r2", "r3", "r4", "r5", "r6");
#else

  /* This is one quarter of G; the function H plus xor/rotate. */
#define P(u, v, w, rr) \
  (u) = ((u) ^ (v)) ^ (((u) & (v)) << 1); \
  (w) = rotr32((u) ^ (w), rr);

#define G(s, a, b, c, d) \
  P(s[a], s[b], s[d], 8) \
  P(s[c], s[d], s[b], 11) \
  P(s[a], s[b], s[d], 16) \
  P(s[c], s[d], s[b], 31)
#endif

  for (int i = 0; i < ROUNDS; i++)
  {
    /* columns */
    G(ctx->s, 0, 4, 8, 12);
    G(ctx->s, 1, 5, 9, 13);
    G(ctx->s, 2, 6, 10, 14);
    G(ctx->s, 3, 7, 11, 15);

    /* diagonals */
    G(ctx->s, 0, 5, 10, 15);
    G(ctx->s, 1, 6, 11, 12);
    G(ctx->s, 2, 7, 8, 13);
    G(ctx->s, 3, 4, 9, 14);
  }

#undef G
#undef P
}

static void init(norx32_ctx *ctx,
                 const uint8_t key[static 16],
                 const uint8_t nonce[static 8])
{
  /* 1. Basic setup */
  ctx->s[0] = read32_le(nonce + 0);
  ctx->s[1] = read32_le(nonce + 4);
  ctx->s[2] = 0xb707322f;
  ctx->s[3] = 0xa0c7c90d;

  ctx->s[4] = read32_le(key + 0);
  ctx->s[5] = read32_le(key + 4);
  ctx->s[6] = read32_le(key + 8);
  ctx->s[7] = read32_le(key + 12);

  ctx->s[8] = 0xa3d8d930;
  ctx->s[9] = 0x3fa8b72c;
  ctx->s[10] = 0xed84eb49;
  ctx->s[11] = 0xedca4787;

  ctx->s[12] = 0x335463eb;
  ctx->s[13] = 0xf994220b;
  ctx->s[14] = 0xbe0bf5c9;
  ctx->s[15] = 0xd7c49104;

  /* 2. Parameter integration
   * w = 32
   * l = 4
   * p = 1
   * t = 128
   */
  ctx->s[12] ^= WORD_BITS;
  ctx->s[13] ^= ROUNDS;
  ctx->s[14] ^= DEGREE;
  ctx->s[15] ^= TAG_BITS;

  permute(ctx);
}

/* Input domain separation constant for next step, and final permutation of
 * preceeding step. */
static void switch_domain(norx32_ctx *ctx, uint32_t constant)
{
  ctx->s[15] ^= constant;
  permute(ctx);
}

typedef struct
{
  norx32_ctx *ctx;
  uint32_t type;
} blockctx;

static void input_block_final(void *vctx, const uint8_t *data)
{
  blockctx *bctx = vctx;
  norx32_ctx *ctx = bctx->ctx;

  /* just xor-in data. */
  for (int i = 0; i < RATE_WORDS; i++)
  {
    ctx->s[i] ^= read32_le(data);
    data += WORD_BYTES;
  }
}

static void input_block(void *vctx, const uint8_t *data)
{
  /* Process block, then prepare for the next one. */
  blockctx *bctx = vctx;
  input_block_final(vctx, data);
  switch_domain(bctx->ctx, bctx->type);
}

static void input(norx32_ctx *ctx, uint32_t type,
                  const uint8_t *buf, size_t nbuf)
{
  uint8_t partial[RATE_BYTES];
  size_t npartial = 0;
  blockctx bctx = { ctx, type };

  /* Process input. */
  cf_blockwise_accumulate(partial, &npartial, sizeof partial,
                          buf, nbuf,
                          input_block,
                          &bctx);

  /* Now pad partial. This contains the trailing portion of buf. */
  memset(partial + npartial, 0, sizeof(partial) - npartial);
  partial[npartial] = 0x01;
  partial[sizeof(partial) - 1] ^= 0x80;

  input_block_final(&bctx, partial);
}

static void do_header(norx32_ctx *ctx, const uint8_t *buf, size_t nbuf)
{
  if (nbuf)
  {
    switch_domain(ctx, DOMAIN_HEADER);
    input(ctx, DOMAIN_HEADER, buf, nbuf);
  }
}

static void do_trailer(norx32_ctx *ctx, const uint8_t *buf, size_t nbuf)
{
  if (nbuf)
  {
    switch_domain(ctx, DOMAIN_TRAILER);
    input(ctx, DOMAIN_TRAILER, buf, nbuf);
  }
}

static void body_block_encrypt(norx32_ctx *ctx,
                               const uint8_t plain[static RATE_BYTES],
                               uint8_t cipher[static RATE_BYTES])
{
  for (int i = 0; i < RATE_WORDS; i++)
  {
    ctx->s[i] ^= read32_le(plain);
    write32_le(ctx->s[i], cipher);
    plain += WORD_BYTES;
    cipher += WORD_BYTES;
  }
}

static void encrypt_body(norx32_ctx *ctx,
                         const uint8_t *plain, uint8_t *cipher, size_t nbytes)
{
  if (nbytes == 0)
    return;

  /* Process full blocks: easy */
  while (nbytes >= RATE_BYTES)
  {
    switch_domain(ctx, DOMAIN_PAYLOAD);
    body_block_encrypt(ctx, plain, cipher);
    plain += RATE_BYTES;
    cipher += RATE_BYTES;
    nbytes -= RATE_BYTES;
  }

  /* Final padded block. */
  uint8_t partial[RATE_BYTES];
  memset(partial, 0, sizeof partial);
  memcpy(partial, plain, nbytes);
  partial[nbytes] ^= 0x01;
  partial[sizeof(partial) - 1] ^= 0x80;

  switch_domain(ctx, DOMAIN_PAYLOAD);
  body_block_encrypt(ctx, partial, partial);

  memcpy(cipher, partial, nbytes);
}

static void body_block_decrypt(norx32_ctx *ctx,
                               const uint8_t cipher[static RATE_BYTES],
                               uint8_t plain[static RATE_BYTES],
                               size_t start, size_t end)
{
  for (size_t i = start; i < end; i++)
  {
    uint32_t ct = read32_le(cipher);
    write32_le(ctx->s[i] ^ ct, plain);
    ctx->s[i] = ct;
    plain += WORD_BYTES;
    cipher += WORD_BYTES;
  }
}

static void undo_padding(norx32_ctx *ctx, size_t bytes)
{
  assert(bytes < RATE_BYTES);
  ctx->s[bytes / WORD_BYTES] ^= 0x01 << ((bytes % WORD_BYTES) * 8);
  ctx->s[RATE_WORDS - 1] ^= 0x80000000;
}

static void decrypt_body(norx32_ctx *ctx,
                         const uint8_t *cipher, uint8_t *plain, size_t nbytes)
{
  if (nbytes == 0)
    return;

  /* Process full blocks. */
  while (nbytes >= RATE_BYTES)
  {
    switch_domain(ctx, DOMAIN_PAYLOAD);
    body_block_decrypt(ctx, cipher, plain, 0, RATE_WORDS);
    plain += RATE_BYTES;
    cipher += RATE_BYTES;
    nbytes -= RATE_BYTES;
  }

  /* Then partial blocks. */
  size_t offset = 0;
  switch_domain(ctx, DOMAIN_PAYLOAD);

  undo_padding(ctx, nbytes);

  /* In units of whole words. */
  while (nbytes >= WORD_BYTES)
  {
    body_block_decrypt(ctx, cipher, plain, offset, offset + 1);
    plain += WORD_BYTES;
    cipher += WORD_BYTES;
    nbytes -= WORD_BYTES;
    offset += 1;
  }

  /* And then, finally, bytewise. */
  uint8_t tmp[WORD_BYTES];
  write32_le(ctx->s[offset], tmp);

  for (size_t i = 0; i < nbytes; i++)
  {
    uint8_t c = cipher[i];
    plain[i] = tmp[i] ^ c;
    tmp[i] = c;
  }

  ctx->s[offset] = read32_le(tmp);
}

static void get_tag(norx32_ctx *ctx, uint8_t tag[static 16])
{
  switch_domain(ctx, DOMAIN_TAG);
  permute(ctx);
  write32_le(ctx->s[0], tag + 0);
  write32_le(ctx->s[1], tag + 4);
  write32_le(ctx->s[2], tag + 8);
  write32_le(ctx->s[3], tag + 12);
}

void cf_norx32_encrypt(const uint8_t key[static 16],
                       const uint8_t nonce[static 8],
                       const uint8_t *header, size_t nheader,
                       const uint8_t *plaintext, size_t nbytes,
                       const uint8_t *trailer, size_t ntrailer,
                       uint8_t *ciphertext,
                       uint8_t tag[static 16])
{
  norx32_ctx ctx;

  init(&ctx, key, nonce);
  do_header(&ctx, header, nheader);
  encrypt_body(&ctx, plaintext, ciphertext, nbytes);
  do_trailer(&ctx, trailer, ntrailer);
  get_tag(&ctx, tag);

  mem_clean(&ctx, sizeof ctx);
}

int cf_norx32_decrypt(const uint8_t key[static 16],
                      const uint8_t nonce[static 8],
                      const uint8_t *header, size_t nheader,
                      const uint8_t *ciphertext, size_t nbytes,
                      const uint8_t *trailer, size_t ntrailer,
                      const uint8_t tag[static 16],
                      uint8_t *plaintext)
{
  norx32_ctx ctx;
  uint8_t ourtag[16];

  init(&ctx, key, nonce);
  do_header(&ctx, header, nheader);
  decrypt_body(&ctx, ciphertext, plaintext, nbytes);
  do_trailer(&ctx, trailer, ntrailer);
  get_tag(&ctx, ourtag);

  int err = 0;

  if (!mem_eq(ourtag, tag, sizeof ourtag))
  {
    err = 1;
    mem_clean(plaintext, nbytes);
    mem_clean(ourtag, sizeof ourtag);
  }

  mem_clean(&ctx, sizeof ctx);
  return err;
}
