/**
 * @file
 * Core cryptographic operations, i.e. permutations of the sponge state.
 *
 * @license Creative Commons Zero (CC0) 1.0
 * @authors see AUTHORS.md file
 */

#include "ascon.h"
/* Linter warnings about #include "ascon_internal.h" being unused are WRONG.
 * If you do not include the header, the linker cannot find the references. */
#include "ascon_internal.h"


// 12-round permutation starts here
#define ROUND_CONSTANT_01 0xF0
#define ROUND_CONSTANT_02 0xE1
#define ROUND_CONSTANT_03 0xD2
#define ROUND_CONSTANT_04 0xC3

// 8-round permutation starts here
#define ROUND_CONSTANT_05 0xB4
#define ROUND_CONSTANT_06 0xA5

// 6-round permutation starts here
#define ROUND_CONSTANT_07 0x96
#define ROUND_CONSTANT_08 0x87
#define ROUND_CONSTANT_09 0x78
#define ROUND_CONSTANT_10 0x69
#define ROUND_CONSTANT_11 0x5A
#define ROUND_CONSTANT_12 0x4B

/** Bit-shift and rotation of a uint64_t to the right by n bits. */
#define ASCON_ROTR64(x, n) (((x) << (64U - (n)) ) | ((x) >> (n)))

/**
 * @internal
 * Performs one permutation round on the Ascon sponge for the given round
 * constant.
 *
 * Although this function is never used outside of this file,
 * it is NOT marked as static, as it is generally inline in the functions
 * using it to increase the performance. Inlining static functions into
 * functions used outside of this file leads to compilation errors:
 * "error: static function 'ascon_round' is used in an inline function with
 * external linkage [-Werror,-Wstatic-in-inline]".
 */
ASCON_INLINE void
ascon_round(ascon_sponge_t* sponge,
            const uint_fast8_t round_const)
{
    ascon_sponge_t temp;
    // addition of round constant
    sponge->x2 ^= round_const;
    // substitution layer
    sponge->x0 ^= sponge->x4;
    sponge->x4 ^= sponge->x3;
    sponge->x2 ^= sponge->x1;
    // start of keccak s-box
    temp.x0 = ~sponge->x0;
    temp.x1 = ~sponge->x1;
    temp.x2 = ~sponge->x2;
    temp.x3 = ~sponge->x3;
    temp.x4 = ~sponge->x4;
    temp.x0 &= sponge->x1;
    temp.x1 &= sponge->x2;
    temp.x2 &= sponge->x3;
    temp.x3 &= sponge->x4;
    temp.x4 &= sponge->x0;
    sponge->x0 ^= temp.x1;
    sponge->x1 ^= temp.x2;
    sponge->x2 ^= temp.x3;
    sponge->x3 ^= temp.x4;
    sponge->x4 ^= temp.x0;
    // end of keccak s-box
    sponge->x1 ^= sponge->x0;
    sponge->x0 ^= sponge->x4;
    sponge->x3 ^= sponge->x2;
    sponge->x2 = ~sponge->x2;
    // linear diffusion layer
    sponge->x0 ^= ASCON_ROTR64(sponge->x0, 19) ^ ASCON_ROTR64(sponge->x0, 28);
    sponge->x1 ^= ASCON_ROTR64(sponge->x1, 61) ^ ASCON_ROTR64(sponge->x1, 39);
    sponge->x2 ^= ASCON_ROTR64(sponge->x2, 1) ^ ASCON_ROTR64(sponge->x2, 6);
    sponge->x3 ^= ASCON_ROTR64(sponge->x3, 10) ^ ASCON_ROTR64(sponge->x3, 17);
    sponge->x4 ^= ASCON_ROTR64(sponge->x4, 7) ^ ASCON_ROTR64(sponge->x4, 41);
}

ASCON_INLINE void
ascon_permutation_a12(ascon_sponge_t* const sponge)
{
    ascon_round(sponge, ROUND_CONSTANT_01);
    ascon_round(sponge, ROUND_CONSTANT_02);
    ascon_round(sponge, ROUND_CONSTANT_03);
    ascon_round(sponge, ROUND_CONSTANT_04);
    ascon_round(sponge, ROUND_CONSTANT_05);
    ascon_round(sponge, ROUND_CONSTANT_06);
    ascon_round(sponge, ROUND_CONSTANT_07);
    ascon_round(sponge, ROUND_CONSTANT_08);
    ascon_round(sponge, ROUND_CONSTANT_09);
    ascon_round(sponge, ROUND_CONSTANT_10);
    ascon_round(sponge, ROUND_CONSTANT_11);
    ascon_round(sponge, ROUND_CONSTANT_12);
}

ASCON_INLINE void
ascon_permutation_b8(ascon_sponge_t* const sponge)
{
    ascon_round(sponge, ROUND_CONSTANT_05);
    ascon_round(sponge, ROUND_CONSTANT_06);
    ascon_round(sponge, ROUND_CONSTANT_07);
    ascon_round(sponge, ROUND_CONSTANT_08);
    ascon_round(sponge, ROUND_CONSTANT_09);
    ascon_round(sponge, ROUND_CONSTANT_10);
    ascon_round(sponge, ROUND_CONSTANT_11);
    ascon_round(sponge, ROUND_CONSTANT_12);
}

ASCON_INLINE void
ascon_permutation_b6(ascon_sponge_t* const sponge)
{
    ascon_round(sponge, ROUND_CONSTANT_07);
    ascon_round(sponge, ROUND_CONSTANT_08);
    ascon_round(sponge, ROUND_CONSTANT_09);
    ascon_round(sponge, ROUND_CONSTANT_10);
    ascon_round(sponge, ROUND_CONSTANT_11);
    ascon_round(sponge, ROUND_CONSTANT_12);
}
