text: /* siphash.c
   *
   * Copyright (C) 2006-2021 wolfSSL Inc.
   *
   * This file is part of wolfSSL.
   *
   * wolfSSL is free software; you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation; either version 2 of the License, or
   * (at your option) any later version.
   *
   * wolfSSL is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with this program; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
   */
  
  
  #ifdef HAVE_CONFIG_H
      #include <config.h>
  #endif
  
  #include <wolfssl/wolfcrypt/settings.h>
  #include <wolfssl/wolfcrypt/types.h>
  
  #include <wolfssl/wolfcrypt/siphash.h>
  #include <wolfssl/wolfcrypt/error-crypt.h>
  
  #ifdef NO_INLINE
      #include <wolfssl/wolfcrypt/misc.h>
  #else
      #define WOLFSSL_MISC_INCLUDED
      #include <wolfcrypt/src/misc.c>
  #endif
  
  
  /* DESCRIPTION
   *
   * SipHash is a PseudoRandom Function (PRF) that can be used with small
   * messages (less than 256 bytes).
   * SipHash can be used for Message Authentication Codes (MACs) and as such must
   * be passed a secret key.
   * https://eprint.iacr.org/2012/351.pdf
   *
   * SipHash is commonly used in hash tables.
   * Do not use this as a hash not as a general purpose MAC.
   *
   * WOLFSSL_SIPHASH_CROUNDS and WOLFSSL_SIPHASH_DROUNDS can be defined at build
   * time to change the algorithm.
   * Default is SipHash-2-4:
   *   WOLFSSL_SIPHASH_CROUNDS = 2
   *   WOLFSSL_SIPHASH_DROUNDS = 4
   *
   * Inline assembly implementations of wc_SipHash() written for:
   *   - GCC for Intel x86_64
   *   - GCC for Aarch64.
   */
  
  #ifdef WOLFSSL_SIPHASH
  
  #ifdef LITTLE_ENDIAN_ORDER
  /**
   * Decode little-endian byte array to 64-bit number.
   *
   * @param [in] a  Little-endian byte array.
   * @return 64-bit number.
   */
  #define GET_U64(a)      (*(word64*)(a))
  /**
   * Decode little-endian byte array to 32-bit number.
   *
   * @param [in] a  Little-endian byte array.
   * @return 32-bit number.
   */
  #define GET_U32(a)      (*(word32*)(a))
  /**
   * Decode little-endian byte array to 16-bit number.
   *
   * @param [in] a  Little-endian byte array.
   * @return 16-bit number.
   */
  #define GET_U16(a)      (*(word16*)(a))
  /**
   * Encode 64-bit nuumber to a little-endian byte array.
   *
   * @param [out] a  Byte array to write into.
   * @param [in]  n  Number to encode.
   */
  #define SET_U64(a, n)   ((*(word64*)(a)) = n)
  #else
  /**
   * Decode little-endian byte array to 64-bit number.
   *
   * @param [in] a  Little-endian byte array.
   * @return 64-bit number.
   */
  #define GET_U64(a)      (((word64)((a)[7]) << 56) |     \
                           ((word64)((a)[6]) << 48) |     \
                           ((word64)((a)[5]) << 40) |     \
                           ((word64)((a)[4]) << 32) |     \
                           ((word64)((a)[3]) << 24) |     \
                           ((word64)((a)[2]) << 16) |     \
                           ((word64)((a)[1]) <<  8) |     \
                           ((word64)((a)[0])      ))
  /**
   * Decode little-endian byte array to 32-bit number.
   *
   * @param [in] a  Little-endian byte array.
   * @return 32-bit number.
   */
  #define GET_U32(a)      (((word64)((a)[3]) << 24) |     \
                           ((word32)((a)[2]) << 16) |     \
                           ((word32)((a)[1]) <<  8) |     \
                           ((word32)((a)[0])      ))
  /**
   * Decode little-endian byte array to 16-bit number.
   *
   * @param [in] a  Little-endian byte array.
   * @return 16-bit number.
   */
  #define GET_U16(a)      (((word16)((a)[1]) <<  8) |     \
                           ((word16)((a)[0])      ))
  /**
   * Encode 64-bit nuumber to a little-endian byte array.
   *
   * @param [out] a  Byte array to write into.
   * @param [in]  n  Number to encode.
   */
  #define SET_U64(a, n)   (a)[0] = (byte)((n)      );     \
                          (a)[1] = (byte)((n) >>  8);     \
                          (a)[2] = (byte)((n) >> 16);     \
                          (a)[3] = (byte)((n) >> 24);     \
                          (a)[4] = (byte)((n) >> 32);     \
                          (a)[5] = (byte)((n) >> 40);     \
                          (a)[6] = (byte)((n) >> 48);     \
                          (a)[7] = (byte)((n) >> 56)
  #endif
  
  /**
   * Initialize SipHash operation with a key.
   *
   * @param [out] sipHash  SipHash object.
   * @param [in]  key      16 byte array - little endian.
   * @return  BAD_FUNC_ARG when sipHash or key is NULL.
   * @return  BAD_FUNC_ARG when outSz is neither 8 nor 16.
   * @return  0 on success.
   */
  int wc_InitSipHash(SipHash* sipHash, const unsigned char* key,
      unsigned char outSz)
  {
      int ret = 0;
  
      /* Validate parameters. */
      if ((sipHash == NULL) || (key == NULL) ||
          ((outSz != SIPHASH_MAC_SIZE_8) && (outSz != SIPHASH_MAC_SIZE_16))) {
          ret = BAD_FUNC_ARG;
      }
  
      if (ret == 0) {
          word64 k0 = GET_U64(key + 0);
          word64 k1 = GET_U64(key + 8);
  
          /* Initialize state with key. */
          sipHash->v[0] = 0x736f6d6570736575UL;
          if (outSz == SIPHASH_MAC_SIZE_8) {
              sipHash->v[1] = 0x646f72616e646f6dUL;
          }
          else {
              sipHash->v[1] = 0x646f72616e646f83UL;
          }
          sipHash->v[2] = 0x6c7967656e657261UL;
          sipHash->v[3] = 0x7465646279746573UL;
  
          sipHash->v[0] ^= k0;
          sipHash->v[1] ^= k1;
          sipHash->v[2] ^= k0;
          sipHash->v[3] ^= k1;
  
          /* No cached message bytes. */
          sipHash->cacheCnt = 0;
          /* No message bytes compressed yet. */
          sipHash->inCnt = 0;
          /* Keep the output size to check against final call. */
          sipHash->outSz = outSz;
      }
  
      return ret;
  }
  
  /**
   * One round of SipHash.
   *
   * @param [in, out] sipHash  SipHash object.
   */
  static WC_INLINE void SipRound(SipHash *sipHash)
  {
      word64* v = sipHash->v;
  
      v[0] += v[1];
      v[2] += v[3];
      v[1] = rotlFixed64(v[1], 13);
      v[3] = rotlFixed64(v[3], 16);
      v[1] ^= v[0];
      v[3] ^= v[2];
      v[0] = rotlFixed64(v[0], 32);
      v[2] += v[1];
      v[0] += v[3];
      v[1] = rotlFixed64(v[1], 17);
      v[3] = rotlFixed64(v[3], 21);
      v[1] ^= v[2];
      v[3] ^= v[0];
      v[2] = rotlFixed64(v[2], 32);
  }
  
  /**
   * One step of the compression operation.
   *
   * @param [in, out] sipHash  SipHash object.
   * @param [in]      m        Message to compress.
   */
  static WC_INLINE void SipHashCompress(SipHash* sipHash, const byte* m)
  {
      int i;
  
      sipHash->v[3] ^= GET_U64(m);
      for (i = 0; i < WOLFSSL_SIPHASH_CROUNDS; i++) {
          SipRound(sipHash);
      }
      sipHash->v[0] ^= GET_U64(m);
  }
  
  /**
   * Update the SipHash operation with more data.
   *
   * @param [in, out] sipHash  SipHash object.
   * @param [in]      in       Input message.
   * @param [in]      inSz     Size of input message.
   * @return  BAD_FUNC_ARG when sipHash is NULL.
   * @return  BAD_FUNC_ARG when in is NULL and inSz is not zero.
   * @return  0 on success.
   */
  int wc_SipHashUpdate(SipHash* sipHash, const unsigned char* in, word32 inSz)
  {
      int ret = 0;
  
      /* Validate parameters. */
      if ((sipHash == NULL) || ((in == NULL) && (inSz != 0))) {
          ret = BAD_FUNC_ARG;
      }
  
      /* Process any message bytes. */
      if ((ret == 0) && (inSz > 0)) {
          /* Add to cache if already started. */
          if (sipHash->cacheCnt > 0) {
              byte len = SIPHASH_BLOCK_SIZE - sipHash->cacheCnt;
              if (len > inSz) {
                  len = inSz;
              }
              XMEMCPY(sipHash->cache + sipHash->cacheCnt, in, len);
              in += len;
              inSz -= len;
              sipHash->cacheCnt += len;
  
              if (sipHash->cacheCnt == SIPHASH_BLOCK_SIZE) {
                  /* Compress the block from the cache. */
                  SipHashCompress(sipHash, sipHash->cache);
                  sipHash->cacheCnt = 0;
              }
          }
  
          /* Process more blocks from message. */
          while (inSz >= SIPHASH_BLOCK_SIZE) {
              /* Compress the next block from the message data. */
              SipHashCompress(sipHash, in);
              in += SIPHASH_BLOCK_SIZE;
              inSz -= SIPHASH_BLOCK_SIZE;
              sipHash->inCnt += SIPHASH_BLOCK_SIZE;
          }
  
          if (inSz > 0) {
              /* Cache remaining message bytes less than a block. */
              XMEMCPY(sipHash->cache, in, inSz);
              sipHash->cacheCnt = inSz;
          }
      }
  
      return ret;
  }
  
  /**
   * Calculate 8-bytes of output.
   *
   * @param [in, out] sipHash  SipHash object.
   * @param [out]     out      Buffer to place 8-bytes of MAC into.
   */
  static WC_INLINE void SipHashOut(SipHash* sipHash, byte* out)
  {
      word64 n;
      int i;
  
      for (i = 0; i < WOLFSSL_SIPHASH_DROUNDS; i++) {
          SipRound(sipHash);
      }
      n = sipHash->v[0] ^ sipHash->v[1] ^ sipHash->v[2] ^ sipHash->v[3];
      SET_U64(out, n);
  }
  
  /**
   * Finalize SipHash operation.
   *
   * @param [in, out] sipHash  SipHash object.
   * @param [out]     out      Buffer to place MAC into.
   * @param [in]      outSz    Size of ouput MAC. 8 or 16 only.
   * @return  BAD_FUNC_ARG when sipHash or out is NULL.
   * @return  BAD_FUNC_ARG when outSz is not the same as initialized value.
   * @return  0 on success.
   */
  int wc_SipHashFinal(SipHash* sipHash, unsigned char* out, unsigned char outSz)
  {
      int ret = 0;
  
      /* Validate parameters. */
      if ((sipHash == NULL) || (out == NULL) || (outSz != sipHash->outSz)) {
          ret = BAD_FUNC_ARG;
      }
  
      if (ret == 0) {
          /* Put int remaining cached message bytes. */
          XMEMSET(sipHash->cache + sipHash->cacheCnt, 0, 7 - sipHash->cacheCnt);
          sipHash->cache[7] = (byte)(sipHash->inCnt + sipHash->cacheCnt);
  
          SipHashCompress(sipHash, sipHash->cache);
          sipHash->cacheCnt = 0;
  
          /* Output either 8 or 16 bytes. */
          if (outSz == SIPHASH_MAC_SIZE_8) {
              sipHash->v[2] ^= (word64)0xff;
              SipHashOut(sipHash, out);
          }
          else {
              sipHash->v[2] ^= (word64)0xee;
              SipHashOut(sipHash, out);
              sipHash->v[1] ^= (word64)0xdd;
              SipHashOut(sipHash, out + 8);
          }
      }
  
      return ret;
  }
  
  #if defined(__GNUC__) && defined(__x86_64__) && \
      (WOLFSSL_SIPHASH_CROUNDS == 1 || WOLFSSL_SIPHASH_CROUNDS == 2) && \
      (WOLFSSL_SIPHASH_DROUNDS == 2 || WOLFSSL_SIPHASH_DROUNDS == 4)
  
  #define SIPHASH_ROUND(v0, v1, v2, v3)   \
          "addq   " #v1 ", " #v0 "\n\t"   \
          "addq   " #v3 ", " #v2 "\n\t"   \
          "rolq   $13, " #v1 "\n\t"       \
          "rolq   $16, " #v3 "\n\t"       \
          "xorq   " #v0 ", " #v1 "\n\t"   \
          "xorq   " #v2 ", " #v3 "\n\t"   \
          "rolq   $32, " #v0 "\n\t"       \
          "addq   " #v1 ", " #v2 "\n\t"   \
          "addq   " #v3 ", " #v0 "\n\t"   \
          "rolq   $17, " #v1 "\n\t"       \
          "rolq   $21, " #v3 "\n\t"       \
          "xorq   " #v2 ", " #v1 "\n\t"   \
          "xorq   " #v0 ", " #v3 "\n\t"   \
          "rolq   $32, " #v2 "\n\t"
  
  #define SIPHASH_LAST_ROUND(v0, v1, v2, v3)  \
          "addq   " #v1 ", " #v0 "\n\t"       \
          "addq   " #v3 ", " #v2 "\n\t"       \
          "rolq   $13, " #v1 "\n\t"           \
          "rolq   $16, " #v3 "\n\t"           \
          "xorq   " #v0 ", " #v1 "\n\t"       \
          "xorq   " #v2 ", " #v3 "\n\t"       \
          "addq   " #v1 ", " #v2 "\n\t"       \
          "rolq   $17, " #v1 "\n\t"           \
          "rolq   $21, " #v3 "\n\t"           \
          "xorq   " #v2 ", " #v1 "\n\t"       \
          "rolq   $32, " #v2 "\n\t"
  
  /**
   * Perform SipHash operation on input with key.
   *
   * @param [in]      key      16 byte array - little endian.
   * @param [in]      in       Input message.
   * @param [in]      inSz     Size of input message.
   * @param [out]     out      Buffer to place MAC into.
   * @param [in]      outSz    Size of ouput MAC. 8 or 16 only.
   * @return  BAD_FUNC_ARG when key or out is NULL.
   * @return  BAD_FUNC_ARG when in is NULL and inSz is not zero.
   * @return  BAD_FUNC_ARG when outSz is neither 8 nor 16.
   * @return  0 on success.
   */
  int wc_SipHash(const unsigned char* key, const unsigned char* in, word32 inSz,
      unsigned char* out, unsigned char outSz)
  {
      if ((key == NULL) || ((in == NULL) && (inSz != 0)) || (out == NULL) ||
              ((outSz != SIPHASH_MAC_SIZE_8) && (outSz != SIPHASH_MAC_SIZE_16))) {
          return BAD_FUNC_ARG;
      }
  
      /* v0=%r8, v1=%r9, v2=%r10, v3=%r11 */
      __asm__ __volatile__ (
          "movq   (%[key]), %%r12\n\t"
          "movq   8(%[key]), %%r13\n\t"
  
          "movabsq        $0x736f6d6570736575, %%r8\n\t"
          "movabsq        $0x646f72616e646f6d, %%r9\n\t"
          "movabsq        $0x6c7967656e657261, %%r10\n\t"
          "movabsq        $0x7465646279746573, %%r11\n\t"
  
          "xorq   %%r12, %%r8\n\t"
          "xorq   %%r13, %%r9\n\t"
          "xorq   %%r12, %%r10\n\t"
          "xorq   %%r13, %%r11\n\t"
  
          "cmp    $8, %[outSz]\n\t"
          "mov    %[inSz], %%r13d\n\t"
          "je     L_siphash_8_top\n\t"
          "xorq   $0xee, %%r9\n\t"
          "L_siphash_8_top:\n\t"
  
          "sub    $8, %[inSz]\n\t"
          "jb     L_siphash_done_input_8\n\t"
          "L_siphash_input:\n\t"
          "movq   (%[in]), %%r12\n\t"
          "addq   $8, %[in]\n\t"
          "xorq   %%r12, %%r11\n\t"
  #if WOLFSSL_SIPHASH_CROUNDS == 1
          SIPHASH_ROUND(%%r8, %%r9, %%r10, %%r11)
  #elif WOLFSSL_SIPHASH_CROUNDS == 2
          SIPHASH_ROUND(%%r8, %%r9, %%r10, %%r11)
          SIPHASH_ROUND(%%r8, %%r9, %%r10, %%r11)
  #endif
          "xorq   %%r12, %%r8\n\t"
          "sub    $8, %[inSz]\n\t"
          "jge    L_siphash_input\n\t"
          "L_siphash_done_input_8:\n\t"
          "add    $8, %[inSz]\n\t"
  
          "shlq   $56, %%r13\n\t"
          "cmp    $0, %[inSz]\n\t"
          "je     L_siphash_last_done\n\t"
          "cmp    $4, %[inSz]\n\t"
          "jl     L_siphash_last_lt4\n\t"
  
          "cmp    $7, %[inSz]\n\t"
          "jl     L_siphash_n7\n\t"
          "movzxb 6(%[in]), %%r12\n\t"
          "shlq   $48, %%r12\n\t"
          "orq    %%r12, %%r13\n\t"
          "L_siphash_n7:\n\t"
  
          "cmp    $6, %[inSz]\n\t"
          "jl     L_siphash_n6\n\t"
          "movzxb 5(%[in]), %%r12\n\t"
          "shlq   $40, %%r12\n\t"
          "orq    %%r12, %%r13\n\t"
          "L_siphash_n6:\n\t"
  
          "cmp    $5, %[inSz]\n\t"
          "jl     L_siphash_n5\n\t"
          "movzxb 4(%[in]), %%r12\n\t"
          "shlq   $32, %%r12\n\t"
          "orq    %%r12, %%r13\n\t"
          "L_siphash_n5:\n\t"
  
          "mov    (%[in]), %%r12d\n\t"
          "orq    %%r12, %%r13\n\t"
          "jmp    L_siphash_last_done\n\t"
  
          "L_siphash_last_lt4:\n\t"
  
          "cmp    $1, %[inSz]\n\t"
          "je     L_siphash_last_1\n\t"
  
          "cmp    $3, %[inSz]\n\t"
          "jl     L_siphash_n3\n\t"
          "movzxb 2(%[in]), %%r12\n\t"
          "shlq   $16, %%r12\n\t"
          "orq    %%r12, %%r13\n\t"
          "L_siphash_n3:\n\t"
  
          "movw   (%[in]), %%r12w\n\t"
          "or     %%r12w, %%r13w\n\t"
          "jmp    L_siphash_last_done\n\t"
  
          "L_siphash_last_1:\n\t"
          "movb   (%[in]), %%r12b\n\t"
          "or     %%r12b, %%r13b\n\t"
  
          "L_siphash_last_done:\n\t"
  
          "xorq   %%r13, %%r11\n\t"
  #if WOLFSSL_SIPHASH_CROUNDS == 1
          SIPHASH_ROUND(%%r8, %%r9, %%r10, %%r11)
  #elif WOLFSSL_SIPHASH_CROUNDS == 2
          SIPHASH_ROUND(%%r8, %%r9, %%r10, %%r11)
          SIPHASH_ROUND(%%r8, %%r9, %%r10, %%r11)
  #endif
          "xorq   %%r13, %%r8\n\t"
  
          "cmp    $8, %[outSz]\n\t"
          "je     L_siphash_8_end\n\t"
  
          "xor    $0xee, %%r10b\n\t"
  #if WOLFSSL_SIPHASH_DROUNDS == 2
          SIPHASH_ROUND(%%r8, %%r9, %%r10, %%r11)
          SIPHASH_ROUND(%%r8, %%r9, %%r10, %%r11)
  #elif WOLFSSL_SIPHASH_DROUNDS == 4
          SIPHASH_ROUND(%%r8, %%r9, %%r10, %%r11)
          SIPHASH_ROUND(%%r8, %%r9, %%r10, %%r11)
          SIPHASH_ROUND(%%r8, %%r9, %%r10, %%r11)
          SIPHASH_ROUND(%%r8, %%r9, %%r10, %%r11)
  #endif
          "movq   %%r8, %%r12\n\t"
          "xorq   %%r9, %%r12\n\t"
          "xorq   %%r10, %%r12\n\t"
          "xorq   %%r11, %%r12\n\t"
          "movq   %%r12, (%[out])\n\t"
  
          "xor    $0xdd, %%r9b\n\t"
  #if WOLFSSL_SIPHASH_DROUNDS == 2
          SIPHASH_ROUND(%%r8, %%r9, %%r10, %%r11)
          SIPHASH_LAST_ROUND(%%r8, %%r9, %%r10, %%r11)
  #elif WOLFSSL_SIPHASH_DROUNDS == 4
          SIPHASH_ROUND(%%r8, %%r9, %%r10, %%r11)
          SIPHASH_ROUND(%%r8, %%r9, %%r10, %%r11)
          SIPHASH_ROUND(%%r8, %%r9, %%r10, %%r11)
          SIPHASH_LAST_ROUND(%%r8, %%r9, %%r10, %%r11)
  #endif
          "xorq   %%r11, %%r9\n\t"
          "xorq   %%r10, %%r9\n\t"
          "movq   %%r9, 8(%[out])\n\t"
          "jmp    L_siphash_done\n\t"
  
          "L_siphash_8_end:\n\t"
          "xor    $0xff, %%r10b\n\t"
  #if WOLFSSL_SIPHASH_DROUNDS == 2
          SIPHASH_ROUND(%%r8, %%r9, %%r10, %%r11)
          SIPHASH_LAST_ROUND(%%r8, %%r9, %%r10, %%r11)
  #elif WOLFSSL_SIPHASH_DROUNDS == 4
          SIPHASH_ROUND(%%r8, %%r9, %%r10, %%r11)
          SIPHASH_ROUND(%%r8, %%r9, %%r10, %%r11)
          SIPHASH_ROUND(%%r8, %%r9, %%r10, %%r11)
          SIPHASH_LAST_ROUND(%%r8, %%r9, %%r10, %%r11)
  #endif
          "xorq   %%r11, %%r9\n\t"
          "xorq   %%r10, %%r9\n\t"
          "movq   %%r9, (%[out])\n\t"
  
          "L_siphash_done:\n\t"
  
      : [in] "+r" (in), [inSz] "+r" (inSz)
      : [key] "r" (key), [out] "r" (out) , [outSz] "r" (outSz)
      : "memory", "%r8", "%r9", "%r10", "%r11", "%r12", "%r13"
      );
  
      return 0;
  }
  
  #elif defined(__GNUC__) && defined(__aarch64__) && \
      (WOLFSSL_SIPHASH_CROUNDS == 1 || WOLFSSL_SIPHASH_CROUNDS == 2) && \
      (WOLFSSL_SIPHASH_DROUNDS == 2 || WOLFSSL_SIPHASH_DROUNDS == 4)
  
  #define SIPHASH_ROUND(v0, v1, v2, v3)            \
          "add    " #v0 ", " #v0 ", " #v1 "\n\t"   \
          "add    " #v2 ", " #v2 ", " #v3 "\n\t"   \
          "ror    " #v1 ", " #v1 ", #51\n\t"       \
          "ror    " #v3 ", " #v3 ", #48\n\t"       \
          "eor    " #v1 ", " #v1 ", " #v0 "\n\t"   \
          "eor    " #v3 ", " #v3 ", " #v2 "\n\t"   \
          "ror    " #v0 ", " #v0 ", #32\n\t"       \
          "add    " #v2 ", " #v2 ", " #v1 "\n\t"   \
          "add    " #v0 ", " #v0 ", " #v3 "\n\t"   \
          "ror    " #v1 ", " #v1 ", #47\n\t"       \
          "ror    " #v3 ", " #v3 ", #43\n\t"       \
          "eor    " #v1 ", " #v1 ", " #v2 "\n\t"   \
          "eor    " #v3 ", " #v3 ", " #v0 "\n\t"   \
          "ror    " #v2 ", " #v2 ", #32\n\t"
  
  #define SIPHASH_LAST_ROUND(v0, v1, v2, v3)       \
          "add    " #v0 ", " #v0 ", " #v1 "\n\t"   \
          "add    " #v2 ", " #v2 ", " #v3 "\n\t"   \
          "ror    " #v1 ", " #v1 ", #51\n\t"       \
          "ror    " #v3 ", " #v3 ", #48\n\t"       \
          "eor    " #v1 ", " #v1 ", " #v0 "\n\t"   \
          "eor    " #v3 ", " #v3 ", " #v2 "\n\t"   \
          "add    " #v2 ", " #v2 ", " #v1 "\n\t"   \
          "ror    " #v1 ", " #v1 ", #47\n\t"       \
          "ror    " #v3 ", " #v3 ", #43\n\t"       \
          "eor    " #v1 ", " #v1 ", " #v2 "\n\t"   \
          "ror    " #v2 ", " #v2 ", #32\n\t"
  
  /**
   * Perform SipHash operation on input with key.
   *
   * @param [in]      key      16 byte array - little endian.
   * @param [in]      in       Input message.
   * @param [in]      inSz     Size of input message.
   * @param [out]     out      Buffer to place MAC into.
   * @param [in]      outSz    Size of ouput MAC. 8 or 16 only.
   * @return  BAD_FUNC_ARG when key or out is NULL.
   * @return  BAD_FUNC_ARG when in is NULL and inSz is not zero.
   * @return  BAD_FUNC_ARG when outSz is not 8 nor 16.
   * @return  0 on success.
   */
  int wc_SipHash(const unsigned char* key, const unsigned char* in, word32 inSz,
      unsigned char* out, unsigned char outSz)
  {
      if ((key == NULL) || ((in == NULL) && (inSz != 0)) || (out == NULL) ||
              ((outSz != SIPHASH_MAC_SIZE_8) && (outSz != SIPHASH_MAC_SIZE_16))) {
          return BAD_FUNC_ARG;
      }
  
      /* v0=x8, v1=x9, v2=x10, v3=x11 */
      __asm__ __volatile__ (
          "ldp    x12, x13, [%[key]]\n\t"
  
          "mov    x8, #0x6575\n\t"
          "movk   x8, #0x7073, lsl #16\n\t"
          "movk   x8, #0x6d65, lsl #32\n\t"
          "movk   x8, #0x736f, lsl #48\n\t"
          "mov    x9, #0x6f6d\n\t"
          "movk   x9, #0x6e64, lsl #16\n\t"
          "movk   x9, #0x7261, lsl #32\n\t"
          "movk   x9, #0x646f, lsl #48\n\t"
          "mov    x10, #0x7261\n\t"
          "movk   x10, #0x6e65, lsl #16\n\t"
          "movk   x10, #0x6765, lsl #32\n\t"
          "movk   x10, #0x6c79, lsl #48\n\t"
          "mov    x11, #0x6573\n\t"
          "movk   x11, #0x7974, lsl #16\n\t"
          "movk   x11, #0x6462, lsl #32\n\t"
          "movk   x11, #0x7465, lsl #48\n\t"
  
          "eor    x8, x8, x12\n\t"
          "eor    x9, x9, x13\n\t"
          "eor    x10, x10, x12\n\t"
          "eor    x11, x11, x13\n\t"
  
          "mov    w13, %w[inSz]\n\t"
          "cmp    %w[outSz], #8\n\t"
          "b.eq   L_siphash_8_top\n\t"
          "mov    w12, #0xee\n\t"
          "eor    x9, x9, x12\n\t"
          "L_siphash_8_top:\n\t"
  
          "subs   %w[inSz], %w[inSz], #8\n\t"
          "b.mi   L_siphash_done_input_8\n\t"
          "L_siphash_input:\n\t"
          "ldr    x12, [%[in]], #8\n\t"
          "eor    x11, x11, x12\n\t"
  #if WOLFSSL_SIPHASH_CROUNDS == 1
          SIPHASH_ROUND(x8, x9, x10, x11)
  #elif WOLFSSL_SIPHASH_CROUNDS == 2
          SIPHASH_ROUND(x8, x9, x10, x11)
          SIPHASH_ROUND(x8, x9, x10, x11)
  #endif
          "eor    x8, x8, x12\n\t"
          "subs   %w[inSz], %w[inSz], #8\n\t"
          "b.ge   L_siphash_input\n\t"
          "L_siphash_done_input_8:\n\t"
          "add    %w[inSz], %w[inSz], #8\n\t"
  
          "lsl    x13, x13, #56\n\t"
          "cmp    %w[inSz], #0\n\t"
          "b.eq   L_siphash_last_done\n\t"
          "cmp    %w[inSz], #4\n\t"
          "b.lt   L_siphash_last_lt4\n\t"
  
          "cmp    %w[inSz], #7\n\t"
          "b.lt   L_siphash_n7\n\t"
          "ldrb   w12, [%[in], 6]\n\t"
          "orr    x13, x13, x12, lsl 48\n\t"
          "L_siphash_n7:\n\t"
  
          "cmp    %w[inSz], #6\n\t"
          "b.lt   L_siphash_n6\n\t"
          "ldrb   w12, [%[in], 5]\n\t"
          "orr    x13, x13, x12, lsl 40\n\t"
          "L_siphash_n6:\n\t"
  
          "cmp    %w[inSz], #5\n\t"
          "b.lt   L_siphash_n5\n\t"
          "ldrb   w12, [%[in], 4]\n\t"
          "orr    x13, x13, x12, lsl 32\n\t"
          "L_siphash_n5:\n\t"
  
          "ldr    w12, [%[in]]\n\t"
          "orr    x13, x13, x12\n\t"
          "b      L_siphash_last_done\n\t"
  
          "L_siphash_last_lt4:\n\t"
  
          "cmp    %w[inSz], #1\n\t"
          "b.eq   L_siphash_last_1\n\t"
  
          "cmp    %w[inSz], #3\n\t"
          "b.lt   L_siphash_n3\n\t"
          "ldrb   w12, [%[in], 2]\n\t"
          "orr    x13, x13, x12, lsl 16\n\t"
          "L_siphash_n3:\n\t"
  
          "ldrh   w12, [%[in]]\n\t"
          "orr    x13, x13, x12\n\t"
          "b      L_siphash_last_done\n\t"
  
          "L_siphash_last_1:\n\t"
          "ldrb   w12, [%[in]]\n\t"
          "orr    x13, x13, x12\n\t"
  
          "L_siphash_last_done:\n\t"
  
          "eor    x11, x11, x13\n\t"
  #if WOLFSSL_SIPHASH_CROUNDS == 1
          SIPHASH_ROUND(x8, x9, x10, x11)
  #elif WOLFSSL_SIPHASH_CROUNDS == 2
          SIPHASH_ROUND(x8, x9, x10, x11)
          SIPHASH_ROUND(x8, x9, x10, x11)
  #endif
          "eor    x8, x8, x13\n\t"
  
          "cmp    %w[outSz], #8\n\t"
          "b.eq   L_siphash_8_end\n\t"
  
          "mov    w13, #0xee\n\t"
          "eor    x10, x10, x13\n\t"
  #if WOLFSSL_SIPHASH_DROUNDS == 2
          SIPHASH_ROUND(x8, x9, x10, x11)
          SIPHASH_ROUND(x8, x9, x10, x11)
  #elif WOLFSSL_SIPHASH_DROUNDS == 4
          SIPHASH_ROUND(x8, x9, x10, x11)
          SIPHASH_ROUND(x8, x9, x10, x11)
          SIPHASH_ROUND(x8, x9, x10, x11)
          SIPHASH_ROUND(x8, x9, x10, x11)
  #endif
          "eor    x12, x8, x9\n\t"
          "eor    x13, x10, x11\n\t"
          "eor    x12, x12, x13\n\t"
  
          "mov    w13, #0xdd\n\t"
          "eor    x9, x9, x13\n\t"
  #if WOLFSSL_SIPHASH_DROUNDS == 2
          SIPHASH_ROUND(x8, x9, x10, x11)
          SIPHASH_LAST_ROUND(x8, x9, x10, x11)
  #elif WOLFSSL_SIPHASH_DROUNDS == 4
          SIPHASH_ROUND(x8, x9, x10, x11)
          SIPHASH_ROUND(x8, x9, x10, x11)
          SIPHASH_ROUND(x8, x9, x10, x11)
          SIPHASH_LAST_ROUND(x8, x9, x10, x11)
  #endif
          "eor    x13, x11, x9\n\t"
          "eor    x13, x13, x10\n\t"
          "stp    x12, x13, [%[out]]\n\t"
          "b      L_siphash_done\n\t"
  
          "L_siphash_8_end:\n\t"
          "mov    w13, #0xff\n\t"
          "eor    x10, x10, x13\n\t"
  #if WOLFSSL_SIPHASH_DROUNDS == 2
          SIPHASH_ROUND(x8, x9, x10, x11)
          SIPHASH_LAST_ROUND(x8, x9, x10, x11)
  #elif WOLFSSL_SIPHASH_DROUNDS == 4
          SIPHASH_ROUND(x8, x9, x10, x11)
          SIPHASH_ROUND(x8, x9, x10, x11)
          SIPHASH_ROUND(x8, x9, x10, x11)
          SIPHASH_LAST_ROUND(x8, x9, x10, x11)
  #endif
          "eor    x13, x11, x9\n\t"
          "eor    x13, x13, x10\n\t"
          "str    x13, [%[out]]\n\t"
  
          "L_siphash_done:\n\t"
  
      : [in] "+r" (in), [inSz] "+r" (inSz)
      : [key] "r" (key), [out] "r" (out) , [outSz] "r" (outSz)
      : "memory", "x8", "x9", "x10", "x11", "x12", "x13"
      );
  
      return 0;
  }
  
  #else
  
  #define SipRoundV(v0, v1, v2, v3)   \
      v0 += v1;                       \
      v2 += v3;                       \
      v1 = rotlFixed64(v1, 13);       \
      v3 = rotlFixed64(v3, 16);       \
      v1 ^= v0;                       \
      v3 ^= v2;                       \
      v0 = rotlFixed64(v0, 32);       \
      v2 += v1;                       \
      v0 += v3;                       \
      v1 = rotlFixed64(v1, 17);       \
      v3 = rotlFixed64(v3, 21);       \
      v1 ^= v2;                       \
      v3 ^= v0;                       \
      v2 = rotlFixed64(v2, 32);
  
  #define SipHashCompressV(v0, v1, v2, v3, m)             \
      do {                                                \
          int i;                                          \
          v3 ^= m;                                        \
          for (i = 0; i < WOLFSSL_SIPHASH_CROUNDS; i++) { \
              SipRoundV(v0, v1, v2, v3);                  \
          }                                               \
          v0 ^= m;                                        \
      }                                                   \
      while (0)
  
  #define SipHashOutV(v0, v1, v2, v3, out)                \
      do {                                                \
          word64 n;                                       \
          int i;                                          \
                                                          \
          for (i = 0; i < WOLFSSL_SIPHASH_DROUNDS; i++) { \
              SipRoundV(v0, v1, v2, v3);                  \
          }                                               \
          n = v0 ^ v1 ^ v2 ^ v3;                          \
          SET_U64(out, n);                                \
      }                                                   \
      while (0)
  
  /**
   * Perform SipHash operation on input with key.
   *
   * @param [in]      key      16 byte array - little endian.
   * @param [in]      in       Input message.
   * @param [in]      inSz     Size of input message.
   * @param [out]     out      Buffer to place MAC into.
   * @param [in]      outSz    Size of ouput MAC. 8 or 16 only.
   * @return  BAD_FUNC_ARG when key or out is NULL.
   * @return  BAD_FUNC_ARG when in is NULL and inSz is not zero.
   * @return  BAD_FUNC_ARG when outSz is not 8 nor 16.
   * @return  0 on success.
   */
  int wc_SipHash(const unsigned char* key, const unsigned char* in, word32 inSz,
      unsigned char* out, unsigned char outSz)
  {
      int ret = 0;
  
      if ((key == NULL) || ((in == NULL) && (inSz != 0)) || (out == NULL) ||
              ((outSz != SIPHASH_MAC_SIZE_8) && (outSz != SIPHASH_MAC_SIZE_16))) {
          ret = BAD_FUNC_ARG;
      }
  
      if (ret == 0) {
          word64 v0, v1, v2, v3;
          word64 k0 = GET_U64(key + 0);
          word64 k1 = GET_U64(key + 8);
          word64 b = (word64)((word64)inSz << 56);
  
          /* Initialize state with key. */
          v0 = 0x736f6d6570736575UL;
          v1 = 0x646f72616e646f6dUL;
          v2 = 0x6c7967656e657261UL;
          v3 = 0x7465646279746573UL;
  
          if (outSz == SIPHASH_MAC_SIZE_16) {
              v1 ^= 0xee;
          }
  
          v0 ^= k0;
          v1 ^= k1;
          v2 ^= k0;
          v3 ^= k1;
  
          /* Process blocks from message. */
          while (inSz >= SIPHASH_BLOCK_SIZE) {
              word64 m = GET_U64(in);
              /* Compress the next block from the message data. */
              SipHashCompressV(v0, v1, v2, v3, m);
              in += SIPHASH_BLOCK_SIZE;
              inSz -= SIPHASH_BLOCK_SIZE;
          }
  
          switch (inSz) {
              case 7:
                  b |= (word64)in[6] << 48;
                  /* fall-through */
              case 6:
                  b |= (word64)in[5] << 40;
                  /* fall-through */
              case 5:
                  b |= (word64)in[4] << 32;
                  /* fall-through */
              case 4:
                  b |= (word64)GET_U32(in);
                  break;
              case 3:
                  b |= (word64)in[2] << 16;
                  /* fall-through */
              case 2:
                  b |= (word64)GET_U16(in);
                  break;
              case 1:
                  b |= (word64)in[0];
                  break;
              case 0:
                  break;
          }
          SipHashCompressV(v0, v1, v2, v3, b);
  
          /* Output either 8 or 16 bytes. */
          if (outSz == SIPHASH_MAC_SIZE_8) {
              v2 ^= (word64)0xff;
              SipHashOutV(v0, v1, v2, v3, out);
          }
          else {
              v2 ^= (word64)0xee;
              SipHashOutV(v0, v1, v2, v3, out);
              v1 ^= (word64)0xdd;
              SipHashOutV(v0, v1, v2, v3, out + 8);
          }
      }
  
      return ret;
  }
  #endif /* !ASM */
  
  #endif /* WOLFSSL_SIPHASH */
  