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(); } }