<PackageReference Include="BouncyCastle.Cryptography" Version="2.4.0" />

SparkleDigest

public sealed class SparkleDigest : IDigest
Sparkle v1.2, based on the current round 3 submission, https://sparkle-lwc.github.io/ .
using Org.BouncyCastle.Crypto.Engines; using Org.BouncyCastle.Crypto.Utilities; using Org.BouncyCastle.Utilities; using System; using System.Runtime.CompilerServices; namespace Org.BouncyCastle.Crypto.Digests { public sealed class SparkleDigest : IDigest { public enum SparkleParameters { ESCH256, ESCH384 } private const int RATE_BYTES = 16; private const int RATE_WORDS = 4; private string algorithmName; private readonly uint[] state; private readonly byte[] m_buf = new byte[16]; private readonly int DIGEST_BYTES; private readonly int SPARKLE_STEPS_SLIM; private readonly int SPARKLE_STEPS_BIG; private readonly int STATE_WORDS; private int m_bufPos; public string AlgorithmName => algorithmName; public SparkleDigest(SparkleParameters sparkleParameters) { switch (sparkleParameters) { case SparkleParameters.ESCH256: algorithmName = "ESCH-256"; DIGEST_BYTES = 32; SPARKLE_STEPS_SLIM = 7; SPARKLE_STEPS_BIG = 11; STATE_WORDS = 12; break; case SparkleParameters.ESCH384: algorithmName = "ESCH-384"; DIGEST_BYTES = 48; SPARKLE_STEPS_SLIM = 8; SPARKLE_STEPS_BIG = 12; STATE_WORDS = 16; break; default: throw new ArgumentException("Invalid definition of ESCH instance"); } state = new uint[STATE_WORDS]; } public int GetDigestSize() { return DIGEST_BYTES; } public int GetByteLength() { return 16; } public void Update(byte input) { if (m_bufPos == 16) { ProcessBlock(m_buf, 0, SPARKLE_STEPS_SLIM); m_bufPos = 0; } m_buf[m_bufPos++] = input; } public void BlockUpdate(byte[] input, int inOff, int inLen) { Check.DataLength(input, inOff, inLen, "input buffer too short"); if (inLen >= 1) { int num = 16 - m_bufPos; if (inLen <= num) { Array.Copy(input, inOff, m_buf, m_bufPos, inLen); m_bufPos += inLen; } else { int num2 = 0; if (m_bufPos > 0) { Array.Copy(input, inOff, m_buf, m_bufPos, num); ProcessBlock(m_buf, 0, SPARKLE_STEPS_SLIM); num2 += num; } int num3; while ((num3 = inLen - num2) > 16) { ProcessBlock(input, inOff + num2, SPARKLE_STEPS_SLIM); num2 += 16; } Array.Copy(input, inOff + num2, m_buf, 0, num3); m_bufPos = num3; } } } public int DoFinal(byte[] output, int outOff) { Check.OutputLength(output, outOff, DIGEST_BYTES, "output buffer too short"); if (m_bufPos < 16) { state[(STATE_WORDS >> 1) - 1] ^= 16777216; m_buf[m_bufPos] = 128; while (++m_bufPos < 16) { m_buf[m_bufPos] = 0; } } else state[(STATE_WORDS >> 1) - 1] ^= 33554432; ProcessBlock(m_buf, 0, SPARKLE_STEPS_BIG); Pack.UInt32_To_LE(state, 0, 4, output, outOff); if (STATE_WORDS == 16) { SparkleEngine.SparkleOpt16(state, SPARKLE_STEPS_SLIM); Pack.UInt32_To_LE(state, 0, 4, output, outOff + 16); SparkleEngine.SparkleOpt16(state, SPARKLE_STEPS_SLIM); Pack.UInt32_To_LE(state, 0, 4, output, outOff + 32); } else { SparkleEngine.SparkleOpt12(state, SPARKLE_STEPS_SLIM); Pack.UInt32_To_LE(state, 0, 4, output, outOff + 16); } Reset(); return DIGEST_BYTES; } public void Reset() { Arrays.Fill(state, 0); Arrays.Fill(m_buf, 0); m_bufPos = 0; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ProcessBlock(byte[] buf, int off, int steps) { uint num = Pack.LE_To_UInt32(buf, off); uint num2 = Pack.LE_To_UInt32(buf, off + 4); uint num3 = Pack.LE_To_UInt32(buf, off + 8); uint num4 = Pack.LE_To_UInt32(buf, off + 12); uint num5 = ELL(num ^ num3); uint num6 = ELL(num2 ^ num4); state[0] ^= (num ^ num6); state[1] ^= (num2 ^ num5); state[2] ^= (num3 ^ num6); state[3] ^= (num4 ^ num5); state[4] ^= num6; state[5] ^= num5; if (STATE_WORDS == 16) { state[6] ^= num6; state[7] ^= num5; SparkleEngine.SparkleOpt16(state, steps); } else SparkleEngine.SparkleOpt12(state, steps); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint ELL(uint x) { return Integers.RotateRight(x, 16) ^ (x & 65535); } } }