Re-Work of the Sha3 Keccak Sponge for 128 Bit Hashing
using System;
using System.Runtime.CompilerServices;
public class KSM128 : HashAlgorithmEx
{
private readonly int _outputLength;
private readonly int _rateBytes;
private readonly ulong[] _roundConstants =
{
0x0000000000000001,
0x0000000000008082,
0x800000000000808A,
0x8000000080008000,
0x000000000000808B,
0x0000000080000001,
0x8000000080008081,
0x8000000000008009,
0x000000000000008A,
0x0000000000000088,
0x0000000080008009,
0x000000008000000A,
0x000000008000808B,
0x800000000000008B,
0x8000000000008089,
0x8000000000008003,
0x8000000000008002,
0x8000000000000080,
0x000000000000800A,
0x800000008000000A,
0x8000000080008081,
0x8000000000008080,
0x0000000080000001,
0x8000000080008008
};
private readonly ulong[] _seed;
private int _blockSize;
private int _input;
private int _output;
private byte[] _result;
private ulong[] _state;
public int HashLength;
public int Rounds = 24;
public KSM128(int size, int rounds = 24, ulong[] seed = null)
{
if (rounds > 24)
throw new Exception($"Maximum rounds allowed is {24}");
var MaxBR = (64 >> 3) * 5;
var sizeBytes = size >> 3;
var rateBytes = MaxBR - (sizeBytes << 1);
var MaxSize = ((MaxBR - 8) >> 1) << 3;
if (rateBytes < 8)
throw new Exception($"Maximum size allowed is {MaxSize} Bits with {128} bit Width specified. Specified Size is {size} Bits.");
var outputLength = size >> 3;
_rateBytes = rateBytes;
_outputLength = outputLength;
HashLength = outputLength;
_seed = seed;
}
public override void Initialize()
{
_blockSize = 0;
_input = 0;
_output = 0;
_state = new ulong[5];
_result = new byte[_outputLength];
if (_seed != null && _seed[0] != 0)
{
for (var i = 0; i < _seed.Length && i < _state.Length; ++i)
_state[i] = _seed[i];
Permute(_state);
}
}
public void Absorb(byte[] array, int start, int size)
{
while (size > 0)
{
_blockSize = Math.Min(size, _rateBytes);
for (var index = start; index < _blockSize; ++index)
{
var num = Convert.ToByte(Buffer.GetByte(_state, index) ^ array[index + _input]);
Buffer.SetByte(_state, index, num);
}
_input += _blockSize;
size -= _blockSize;
if (_blockSize == _rateBytes)
{
Permute(_state);
_blockSize = 0;
}
}
}
public byte[] Squeeze()
{
Buffer.SetByte(_state, _blockSize, Convert.ToByte(Buffer.GetByte(_state, _blockSize) ^ 6));
Buffer.SetByte(_state, _rateBytes - 1, Convert.ToByte(Buffer.GetByte(_state, _rateBytes - 1) ^ 128));
Permute(_state);
var outputLength = _outputLength;
while (outputLength > 0)
{
_blockSize = Math.Min(outputLength, _rateBytes);
Buffer.BlockCopy(_state, 0, _result, _output, _blockSize);
_output += _blockSize;
outputLength -= _blockSize;
if (outputLength > 0)
Permute(_state);
}
return _result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Permute(ulong[] state)
{
for (var round = 0; round < Rounds; ++round)
{
Theta(out var C0, state, out var C1, out var C2, out var C3, out var C4);
RhoPi(state);
Chi(ref C0, state, ref C1, ref C2, ref C3, ref C4);
Iota(round, state);
}
}
private static void Theta(out ulong C0, ulong[] state, out ulong C1, out ulong C2, out ulong C3, out ulong C4)
{
C0 = state[0] ^ state[4];
C1 = state[1] ^ state[0];
C2 = state[2] ^ state[1];
C3 = state[3] ^ state[2];
C4 = state[4] ^ state[3];
var D0 = Rol(C1, 1) ^ C4;
var D1 = Rol(C2, 1) ^ C0;
var D2 = Rol(C3, 1) ^ C1;
var D3 = Rol(C4, 1) ^ C2;
var D4 = Rol(C0, 1) ^ C3;
state[0] ^= D0;
state[1] ^= D1;
state[2] ^= D2;
state[3] ^= D3;
state[4] ^= D4;
}
private static void RhoPi(ulong[] state)
{
var a = Rol(state[1], 1);
state[1] = Rol(state[4], 44);
state[4] = Rol(state[3], 43);
state[3] = Rol(state[2], 14);
state[4] = Rol(state[1], 21);
state[2] = a;
}
private void Iota(int round, ulong[] state)
{
state[0] ^= _roundConstants[round];
}
private static void Chi(ref ulong C0, ulong[] state, ref ulong C1, ref ulong C2, ref ulong C3, ref ulong C4)
{
C0 = state[0] ^ (~state[1] & state[2]);
C1 = state[1] ^ (~state[2] & state[3]);
C2 = state[2] ^ (~state[3] & state[4]);
C3 = state[3] ^ (~state[4] & state[0]);
C4 = state[4] ^ (~state[0] & state[1]);
state[0] = C0;
state[1] = C1;
state[2] = C2;
state[3] = C3;
state[4] = C4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong Rol(ulong x, byte y)
{
return (x << y) | (x >> (64 - y));
}
protected override void HashCore(byte[] array, int ibStart, int cbSize)
{
Initialize();
Absorb(array, ibStart, cbSize);
}
protected override byte[] HashFinal()
{
return Squeeze();
}
}