/* @(#)sha3.c	1.4 15/12/27 2015 J. Schilling */
#include <schily/mconfig.h>
#ifndef lint
static	UConst char sccsid[] =
	"@(#)sha3.c	1.4 15/12/27 2015 J. Schilling";
#endif
/*
 * SHA3 hash code taken from
 * https://github.com/rhash/RHash/tree/master/librhash
 *
 * Portions Copyright (c) 2015 J. Schilling
 */

/*
 * sha3.c - an implementation of Secure Hash Algorithm 3 (Keccak).
 * based on the
 * The Keccak SHA-3 submission. Submission to NIST (Round 3), 2011
 * by Guido Bertoni, Joan Daemen, Michaël Peeters and Gilles Van Assche
 *
 * Copyright: 2013 Aleksey Kravchenko <rhash.admin@gmail.com>
 *
 * Permission is hereby granted,  free of charge,  to any person  obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction,  including without limitation
 * the rights to  use, copy, modify,  merge, publish, distribute, sublicense,
 * and/or sell copies  of  the Software,  and to permit  persons  to whom the
 * Software is furnished to do so.
 *
 * This program  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.  Use this program  at  your own risk!
 */

#include <schily/assert.h>
#include <schily/string.h>
#include "byte_order.h"
#include <schily/sha3.h>

#ifdef	HAVE_LONGLONG

#if !defined(HAVE_MEMCPY) || !defined(HAVE_MEMSET)
#include <schily/schily.h>
#endif
#if !defined(HAVE_MEMCPY) && !defined(memcpy)
#define	memcpy(s1, s2, n)	movebytes(s2, s1, n)
#endif
#if !defined(HAVE_MEMSET) && !defined(memset)
#define	memset(s, c, n)		fillbytes(s, n, c)
#endif

static void rhash_keccak_init	__PR((sha3_ctx *ctx, unsigned bits));
static void keccak_theta	__PR((UInt64_t *A));
static void keccak_pi		__PR((UInt64_t *A));
static void keccak_chi		__PR((UInt64_t *A));
static void rhash_sha3_permutation __PR((UInt64_t *state));
static void rhash_sha3_process_block __PR((UInt64_t hash[25],
					const UInt64_t *block,
					size_t block_size));

/*
 * The Cygwin compile environment incorrectly implements #pragma weak.
 * The weak symbols are only defined as local symbols making it impossible
 * to use them from outside the scope of this source file.
 * A platform that allows linking with global symbols has HAVE_LINK_WEAK
 * defined.
 */
#if defined(HAVE_PRAGMA_WEAK) && defined(HAVE_LINK_WEAK)
#pragma	weak SHA3_224_Init = rhash_sha3_224_init
#pragma	weak SHA3_256_Init = rhash_sha3_256_init
#pragma	weak SHA3_384_Init = rhash_sha3_384_init
#pragma	weak SHA3_512_Init = rhash_sha3_512_init
#pragma	weak SHA3_Update = rhash_sha3_update
#else

void SHA3_224_Init	__PR((SHA3_CTX *ctx));
void SHA3_256_Init	__PR((SHA3_CTX *ctx));
void SHA3_384_Init	__PR((SHA3_CTX *ctx));
void SHA3_512_Init	__PR((SHA3_CTX *ctx));
void SHA3_Update	__PR((SHA3_CTX *ctx,
				const unsigned char *msg,
				size_t size));

void
SHA3_224_Init(ctx)
	SHA3_CTX	*ctx;
{
	rhash_sha3_224_init(ctx);
}

void
SHA3_256_Init(ctx)
	SHA3_CTX	*ctx;
{
	rhash_sha3_256_init(ctx);
}

void
SHA3_384_Init(ctx)
	SHA3_CTX	*ctx;
{
	rhash_sha3_384_init(ctx);
}

void
SHA3_512_Init(ctx)
	SHA3_CTX	*ctx;
{
	rhash_sha3_512_init(ctx);
}

void
SHA3_Update(ctx, msg, size)
	SHA3_CTX	*ctx;
	const unsigned char *msg;
	size_t		size;
{
	rhash_sha3_update(ctx, msg, size);
}
#endif	/* defined(HAVE_PRAGMA_WEAK) && defined(HAVE_LINK_WEAK) */

/* constants */
#define	NumberOfRounds	24

/* SHA3 (Keccak) constants for 24 rounds */
static UInt64_t keccak_round_constants[NumberOfRounds] = {
	UI64(0x0000000000000001), UI64(0x0000000000008082),
	UI64(0x800000000000808A), UI64(0x8000000080008000),
	UI64(0x000000000000808B), UI64(0x0000000080000001),
	UI64(0x8000000080008081), UI64(0x8000000000008009),
	UI64(0x000000000000008A), UI64(0x0000000000000088),
	UI64(0x0000000080008009), UI64(0x000000008000000A),
	UI64(0x000000008000808B), UI64(0x800000000000008B),
	UI64(0x8000000000008089), UI64(0x8000000000008003),
	UI64(0x8000000000008002), UI64(0x8000000000000080),
	UI64(0x000000000000800A), UI64(0x800000008000000A),
	UI64(0x8000000080008081), UI64(0x8000000000008080),
	UI64(0x0000000080000001), UI64(0x8000000080008008)
};

/* Initializing a sha3 context for given number of output bits */
static void
rhash_keccak_init(ctx, bits)
	sha3_ctx	*ctx;
	unsigned	bits;
{
	/* NB: The Keccak capacity parameter = bits * 2 */
	unsigned rate = 1600 - bits * 2;

	memset(ctx, 0, sizeof (sha3_ctx));
	ctx->block_size = rate / 8;
	assert(rate <= 1600 && (rate % 64) == 0);
}

/*
 * Initialize context before calculating hash.
 *
 * @param ctx context to initialize
 */
void
rhash_sha3_224_init(ctx)
	sha3_ctx	*ctx;
{
	rhash_keccak_init(ctx, 224);
}

/*
 * Initialize context before calculating hash.
 *
 * @param ctx context to initialize
 */
void
rhash_sha3_256_init(ctx)
	sha3_ctx	*ctx;
{
	rhash_keccak_init(ctx, 256);
}

/*
 * Initialize context before calculating hash.
 *
 * @param ctx context to initialize
 */
void
rhash_sha3_384_init(ctx)
	sha3_ctx	*ctx;
{
	rhash_keccak_init(ctx, 384);
}

/*
 * Initialize context before calculating hash.
 *
 * @param ctx context to initialize
 */
void
rhash_sha3_512_init(ctx)
	sha3_ctx	*ctx;
{
	rhash_keccak_init(ctx, 512);
}

/* Keccak theta() transformation */
static void
keccak_theta(A)
	UInt64_t	*A;
{
	unsigned int x;
	UInt64_t C[5], D[5];

	for (x = 0; x < 5; x++) {
		C[x] = A[x] ^ A[x + 5] ^ A[x + 10] ^ A[x + 15] ^ A[x + 20];
	}
	D[0] = ROTL64(C[1], 1) ^ C[4];
	D[1] = ROTL64(C[2], 1) ^ C[0];
	D[2] = ROTL64(C[3], 1) ^ C[1];
	D[3] = ROTL64(C[4], 1) ^ C[2];
	D[4] = ROTL64(C[0], 1) ^ C[3];

	for (x = 0; x < 5; x++) {
		A[x]	  ^= D[x];
		A[x + 5]  ^= D[x];
		A[x + 10] ^= D[x];
		A[x + 15] ^= D[x];
		A[x + 20] ^= D[x];
	}
}

/* Keccak pi() transformation */
static void
keccak_pi(A)
	UInt64_t	*A;
{
	UInt64_t A1;
	A1 = A[1];
	A[ 1] = A[ 6];
	A[ 6] = A[ 9];
	A[ 9] = A[22];
	A[22] = A[14];
	A[14] = A[20];
	A[20] = A[ 2];
	A[ 2] = A[12];
	A[12] = A[13];
	A[13] = A[19];
	A[19] = A[23];
	A[23] = A[15];
	A[15] = A[ 4];
	A[ 4] = A[24];
	A[24] = A[21];
	A[21] = A[ 8];
	A[ 8] = A[16];
	A[16] = A[ 5];
	A[ 5] = A[ 3];
	A[ 3] = A[18];
	A[18] = A[17];
	A[17] = A[11];
	A[11] = A[ 7];
	A[ 7] = A[10];
	A[10] = A1;
	/* note: A[ 0] is left as is */
}

/* Keccak chi() transformation */
static void
keccak_chi(A)
	UInt64_t	*A;
{
	int i;
	for (i = 0; i < 25; i += 5) {
		UInt64_t A0 = A[0 + i], A1 = A[1 + i];
		A[0 + i] ^= ~A1 & A[2 + i];
		A[1 + i] ^= ~A[2 + i] & A[3 + i];
		A[2 + i] ^= ~A[3 + i] & A[4 + i];
		A[3 + i] ^= ~A[4 + i] & A0;
		A[4 + i] ^= ~A0 & A1;
	}
}

static void
rhash_sha3_permutation(state)
	UInt64_t	*state;
{
	int round;
	for (round = 0; round < NumberOfRounds; round++)
	{
		keccak_theta(state);

		/* apply Keccak rho() transformation */
		state[ 1] = ROTL64(state[ 1],  1);
		state[ 2] = ROTL64(state[ 2], 62);
		state[ 3] = ROTL64(state[ 3], 28);
		state[ 4] = ROTL64(state[ 4], 27);
		state[ 5] = ROTL64(state[ 5], 36);
		state[ 6] = ROTL64(state[ 6], 44);
		state[ 7] = ROTL64(state[ 7],  6);
		state[ 8] = ROTL64(state[ 8], 55);
		state[ 9] = ROTL64(state[ 9], 20);
		state[10] = ROTL64(state[10],  3);
		state[11] = ROTL64(state[11], 10);
		state[12] = ROTL64(state[12], 43);
		state[13] = ROTL64(state[13], 25);
		state[14] = ROTL64(state[14], 39);
		state[15] = ROTL64(state[15], 41);
		state[16] = ROTL64(state[16], 45);
		state[17] = ROTL64(state[17], 15);
		state[18] = ROTL64(state[18], 21);
		state[19] = ROTL64(state[19],  8);
		state[20] = ROTL64(state[20], 18);
		state[21] = ROTL64(state[21],  2);
		state[22] = ROTL64(state[22], 61);
		state[23] = ROTL64(state[23], 56);
		state[24] = ROTL64(state[24], 14);

		keccak_pi(state);
		keccak_chi(state);

		/* apply iota(state, round) */
		*state ^= keccak_round_constants[round];
	}
}

/*
 * The core transformation. Process the specified block of data.
 *
 * @param hash the algorithm state
 * @param block the message block to process
 * @param block_size the size of the processed block in bytes
 */
static void
rhash_sha3_process_block(hash, block, block_size)
	UInt64_t	hash[25];
	const UInt64_t	*block;
	size_t		block_size;
{
	/* expanded loop */
	hash[ 0] ^= le2me_64(block[ 0]);
	hash[ 1] ^= le2me_64(block[ 1]);
	hash[ 2] ^= le2me_64(block[ 2]);
	hash[ 3] ^= le2me_64(block[ 3]);
	hash[ 4] ^= le2me_64(block[ 4]);
	hash[ 5] ^= le2me_64(block[ 5]);
	hash[ 6] ^= le2me_64(block[ 6]);
	hash[ 7] ^= le2me_64(block[ 7]);
	hash[ 8] ^= le2me_64(block[ 8]);
	/* if not sha3-512 */
	if (block_size > 72) {
		hash[ 9] ^= le2me_64(block[ 9]);
		hash[10] ^= le2me_64(block[10]);
		hash[11] ^= le2me_64(block[11]);
		hash[12] ^= le2me_64(block[12]);
		/* if not sha3-384 */
		if (block_size > 104) {
			hash[13] ^= le2me_64(block[13]);
			hash[14] ^= le2me_64(block[14]);
			hash[15] ^= le2me_64(block[15]);
			hash[16] ^= le2me_64(block[16]);
			/* if not sha3-256 */
			if (block_size > 136) {
				hash[17] ^= le2me_64(block[17]);
#ifdef FULL_SHA3_FAMILY_SUPPORT
				/* if not sha3-224 */
				if (block_size > 144) {
					hash[18] ^= le2me_64(block[18]);
					hash[19] ^= le2me_64(block[19]);
					hash[20] ^= le2me_64(block[20]);
					hash[21] ^= le2me_64(block[21]);
					hash[22] ^= le2me_64(block[22]);
					hash[23] ^= le2me_64(block[23]);
					hash[24] ^= le2me_64(block[24]);
				}
#endif
			}
		}
	}
	/* make a permutation of the hash */
	rhash_sha3_permutation(hash);
}

#define	SHA3_FINALIZED	0x80000000

/*
 * Calculate message hash.
 * Can be called repeatedly with chunks of the message to be hashed.
 *
 * @param ctx the algorithm context containing current hashing state
 * @param msg message chunk
 * @param size length of the message chunk
 */
void
rhash_sha3_update(ctx, msg, size)
	sha3_ctx		*ctx;
	const unsigned char	*msg;
	size_t			size;
{
	size_t idx = (size_t)ctx->rest;
	size_t block_size = (size_t)ctx->block_size;

	if (ctx->rest & SHA3_FINALIZED)
		return; /* too late for additional input */
	ctx->rest = (unsigned)((ctx->rest + size) % block_size);

	/* fill partial block */
	if (idx) {
		size_t left = block_size - idx;
		memcpy((char *)ctx->message + idx, msg,
			(size < left ? size : left));
		if (size < left)
			return;

		/* process partial block */
		rhash_sha3_process_block(ctx->hash, ctx->message, block_size);
		msg  += left;
		size -= left;
	}
	while (size >= block_size) {
		UInt64_t *aligned_message_block;
		if (IS_ALIGNED_64(msg)) {
			/*
			 * the most common case is processing of an already
			 * aligned message without copying it
			 */
			aligned_message_block = (UInt64_t *)msg;
		} else {
			memcpy(ctx->message, msg, block_size);
			aligned_message_block = ctx->message;
		}

		rhash_sha3_process_block(ctx->hash, aligned_message_block,
					block_size);
		msg  += block_size;
		size -= block_size;
	}
	if (size) {
		memcpy(ctx->message, msg, size); /* save leftovers */
	}
}

/*
 * Store calculated hash into the given array.
 *
 * @param ctx the algorithm context containing current hashing state
 * @param result calculated hash in binary form
 */
void
rhash_sha3_final(ctx, result)
	sha3_ctx	*ctx;
	unsigned char	*result;
{
	size_t digest_length = 100 - ctx->block_size / 2;
	const size_t block_size = ctx->block_size;

	if (!(ctx->rest & SHA3_FINALIZED))
	{
		/* clear the rest of the data queue */
		memset((char *)ctx->message + ctx->rest, 0,
				block_size - ctx->rest);
		((char *)ctx->message)[ctx->rest] |= 0x06;
		((char *)ctx->message)[block_size - 1] |= 0x80;

		/* process final block */
		rhash_sha3_process_block(ctx->hash, ctx->message, block_size);
		ctx->rest = SHA3_FINALIZED; /* mark context as finalized */
	}

	assert(block_size > digest_length);
	if (result) me64_to_le_str(result, ctx->hash, digest_length);
}

void
SHA3_Final(result, ctx)
	UInt8_t		*result;
	SHA3_CTX	*ctx;
{
	rhash_sha3_final(ctx, result);
}

#ifdef USE_KECCAK
/*
 * Store calculated hash into the given array.
 *
 * @param ctx the algorithm context containing current hashing state
 * @param result calculated hash in binary form
 */
void
rhash_keccak_final(sha3_ctx *ctx, unsigned char *result)
{
	size_t digest_length = 100 - ctx->block_size / 2;
	const size_t block_size = ctx->block_size;

	if (!(ctx->rest & SHA3_FINALIZED)) {
		/* clear the rest of the data queue */
		memset((char *)ctx->message + ctx->rest, 0,
				block_size - ctx->rest);
		((char *)ctx->message)[ctx->rest] |= 0x01;
		((char *)ctx->message)[block_size - 1] |= 0x80;

		/* process final block */
		rhash_sha3_process_block(ctx->hash, ctx->message, block_size);
		ctx->rest = SHA3_FINALIZED; /* mark context as finalized */
	}

	assert(block_size > digest_length);
	if (result)
		me64_to_le_str(result, ctx->hash, digest_length);
}
#endif /* USE_KECCAK */

#endif	/* HAVE_LONGLONG */
