Blake3Digest
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Utilities;
using Org.BouncyCastle.Utilities;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace Org.BouncyCastle.Crypto.Digests
{
public sealed class Blake3Digest : IDigest, IMemoable, IXof
{
private const string ERR_OUTPUTTING = "Already outputting";
private const int NUMWORDS = 8;
private const int ROUNDS = 7;
private const int BLOCKLEN = 64;
private const int CHUNKLEN = 1024;
private const int CHUNKSTART = 1;
private const int CHUNKEND = 2;
private const int PARENT = 4;
private const int ROOT = 8;
private const int KEYEDHASH = 16;
private const int DERIVECONTEXT = 32;
private const int DERIVEKEY = 64;
private const int CHAINING0 = 0;
private const int CHAINING1 = 1;
private const int CHAINING2 = 2;
private const int CHAINING3 = 3;
private const int CHAINING4 = 4;
private const int CHAINING5 = 5;
private const int CHAINING6 = 6;
private const int CHAINING7 = 7;
private const int IV0 = 8;
private const int IV1 = 9;
private const int IV2 = 10;
private const int IV3 = 11;
private const int COUNT0 = 12;
private const int COUNT1 = 13;
private const int DATALEN = 14;
private const int FLAGS = 15;
private static readonly byte[] SIGMA = new byte[16] {
2,
6,
3,
10,
7,
0,
4,
13,
1,
11,
12,
5,
9,
14,
15,
8
};
private static readonly uint[] IV = new uint[8] {
1779033703,
3144134277,
1013904242,
2773480762,
1359893119,
2600822924,
528734635,
1541459225
};
private readonly byte[] m_theBuffer = new byte[64];
private readonly uint[] m_theK = new uint[8];
private readonly uint[] m_theChaining = new uint[8];
private readonly uint[] m_theV = new uint[16];
private readonly uint[] m_theM = new uint[16];
private readonly byte[] m_theIndices = new byte[16];
private readonly List<uint[]> m_theStack = new List<uint[]>();
private readonly int m_theDigestLen;
private bool m_outputting;
private long m_outputAvailable;
private int m_theMode;
private int m_theOutputMode;
private int m_theOutputDataLen;
private long m_theCounter;
private int m_theCurrBytes;
private int m_thePos;
public string AlgorithmName => "BLAKE3";
public Blake3Digest()
: this(256)
{
}
public Blake3Digest(int pDigestSize)
{
m_theDigestLen = pDigestSize / 8;
Init(null);
}
public Blake3Digest(Blake3Digest pSource)
{
m_theDigestLen = pSource.m_theDigestLen;
Reset(pSource);
}
public int GetByteLength()
{
return 64;
}
public int GetDigestSize()
{
return m_theDigestLen;
}
public void Init(Blake3Parameters pParams)
{
byte[] array = pParams?.GetKey();
byte[] array2 = pParams?.GetContext();
Reset();
if (array != null) {
InitKey(array);
Arrays.Fill(array, 0);
} else if (array2 != null) {
InitNullKey();
m_theMode = 32;
BlockUpdate(array2, 0, array2.Length);
DoFinal(m_theBuffer, 0);
InitKeyFromContext();
Reset();
} else {
InitNullKey();
m_theMode = 0;
}
}
public void Update(byte b)
{
if (m_outputting)
throw new InvalidOperationException("Already outputting");
if (m_theBuffer.Length - m_thePos == 0) {
CompressBlock(m_theBuffer);
Arrays.Fill(m_theBuffer, 0);
m_thePos = 0;
}
m_theBuffer[m_thePos] = b;
m_thePos++;
}
public void BlockUpdate(byte[] pMessage, int pOffset, int pLen)
{
if (pMessage != null)
BlockUpdate(pMessage.AsSpan(pOffset, pLen));
}
public void BlockUpdate(ReadOnlySpan<byte> input)
{
if (!input.IsEmpty) {
if (m_outputting)
throw new InvalidOperationException("Already outputting");
int length = input.Length;
int num = 0;
ReadOnlySpan<byte> readOnlySpan;
if (m_thePos != 0) {
num = 64 - m_thePos;
if (num >= length) {
input.CopyTo(m_theBuffer.AsSpan(m_thePos));
m_thePos += length;
return;
}
readOnlySpan = input.Slice(0, num);
readOnlySpan.CopyTo(m_theBuffer.AsSpan(m_thePos));
CompressBlock(m_theBuffer);
m_thePos = 0;
Arrays.Fill(m_theBuffer, 0);
}
int num2 = length - 64;
int i;
int num3;
for (i = num; i < num2; i += 64) {
num3 = i;
CompressBlock(input.Slice(num3, input.Length - num3));
}
num3 = i;
readOnlySpan = input.Slice(num3, input.Length - num3);
readOnlySpan.CopyTo(m_theBuffer);
m_thePos += length - i;
}
}
public int DoFinal(byte[] pOutput, int pOutOffset)
{
return OutputFinal(pOutput, pOutOffset, GetDigestSize());
}
public int OutputFinal(byte[] pOut, int pOutOffset, int pOutLen)
{
int result = Output(pOut, pOutOffset, pOutLen);
Reset();
return result;
}
public int Output(byte[] pOut, int pOutOffset, int pOutLen)
{
Check.OutputLength(pOut, pOutOffset, pOutLen, "output buffer too short");
return Output(pOut.AsSpan(pOutOffset, pOutLen));
}
public int DoFinal(Span<byte> output)
{
int digestSize = GetDigestSize();
Check.OutputLength(output, digestSize, "output buffer too short");
return OutputFinal(output.Slice(0, digestSize));
}
public int OutputFinal(Span<byte> output)
{
int result = Output(output);
Reset();
return result;
}
public int Output(Span<byte> output)
{
if (!m_outputting)
CompressFinalBlock(m_thePos);
int length = output.Length;
if (length < 0 || (m_outputAvailable >= 0 && length > m_outputAvailable))
throw new ArgumentException("Insufficient bytes remaining");
int num = length;
int num2 = 0;
Span<byte> span;
if (m_thePos < 64) {
int num3 = System.Math.Min(num, 64 - m_thePos);
span = m_theBuffer.AsSpan(m_thePos, num3);
int num4 = num2;
span.CopyTo(output.Slice(num4, output.Length - num4));
m_thePos += num3;
num2 += num3;
num -= num3;
}
while (num > 0) {
NextOutputBlock();
int num5 = System.Math.Min(num, 64);
span = m_theBuffer.AsSpan(0, num5);
int num4 = num2;
span.CopyTo(output.Slice(num4, output.Length - num4));
m_thePos += num5;
num2 += num5;
num -= num5;
}
m_outputAvailable -= length;
return length;
}
public void Reset()
{
ResetBlockCount();
m_thePos = 0;
m_outputting = false;
Arrays.Fill(m_theBuffer, 0);
}
public void Reset(IMemoable pSource)
{
Blake3Digest blake3Digest = (Blake3Digest)pSource;
m_theCounter = blake3Digest.m_theCounter;
m_theCurrBytes = blake3Digest.m_theCurrBytes;
m_theMode = blake3Digest.m_theMode;
m_outputting = blake3Digest.m_outputting;
m_outputAvailable = blake3Digest.m_outputAvailable;
m_theOutputMode = blake3Digest.m_theOutputMode;
m_theOutputDataLen = blake3Digest.m_theOutputDataLen;
Array.Copy(blake3Digest.m_theChaining, 0, m_theChaining, 0, m_theChaining.Length);
Array.Copy(blake3Digest.m_theK, 0, m_theK, 0, m_theK.Length);
Array.Copy(blake3Digest.m_theM, 0, m_theM, 0, m_theM.Length);
m_theStack.Clear();
foreach (uint[] item in blake3Digest.m_theStack) {
m_theStack.Add(Arrays.Clone(item));
}
Array.Copy(blake3Digest.m_theBuffer, 0, m_theBuffer, 0, m_theBuffer.Length);
m_thePos = blake3Digest.m_thePos;
}
public IMemoable Copy()
{
return new Blake3Digest(this);
}
private void CompressBlock(ReadOnlySpan<byte> block)
{
InitChunkBlock(64, false);
InitM(block);
Compress();
if (m_theCurrBytes == 0)
AdjustStack();
}
private void InitM(ReadOnlySpan<byte> block)
{
Pack.LE_To_UInt32(block, m_theM);
}
private void AdjustStack()
{
long num = m_theCounter;
while (num > 0 && (num & 1) != 1) {
uint[] sourceArray = m_theStack[m_theStack.Count - 1];
m_theStack.RemoveAt(m_theStack.Count - 1);
Array.Copy(sourceArray, 0, m_theM, 0, 8);
Array.Copy(m_theChaining, 0, m_theM, 8, 8);
InitParentBlock();
Compress();
num >>= 1;
}
m_theStack.Add(Arrays.CopyOf(m_theChaining, 8));
}
private void CompressFinalBlock(int pDataLen)
{
InitChunkBlock(pDataLen, true);
InitM(m_theBuffer);
Compress();
ProcessStack();
}
private void ProcessStack()
{
while (m_theStack.Count > 0) {
uint[] sourceArray = m_theStack[m_theStack.Count - 1];
m_theStack.RemoveAt(m_theStack.Count - 1);
Array.Copy(sourceArray, 0, m_theM, 0, 8);
Array.Copy(m_theChaining, 0, m_theM, 8, 8);
InitParentBlock();
if (m_theStack.Count < 1)
SetRoot();
Compress();
}
}
private void Compress()
{
InitIndices();
for (int i = 0; i < 6; i++) {
PerformRound();
PermuteIndices();
}
PerformRound();
AdjustChaining();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void PerformRound()
{
MixG(0, 0, 4, 8, 12);
MixG(1, 1, 5, 9, 13);
MixG(2, 2, 6, 10, 14);
MixG(3, 3, 7, 11, 15);
MixG(4, 0, 5, 10, 15);
MixG(5, 1, 6, 11, 12);
MixG(6, 2, 7, 8, 13);
MixG(7, 3, 4, 9, 14);
}
private void AdjustChaining()
{
if (m_outputting) {
for (int i = 0; i < 8; i++) {
m_theV[i] ^= m_theV[i + 8];
m_theV[i + 8] ^= m_theChaining[i];
}
Pack.UInt32_To_LE(m_theV, m_theBuffer, 0);
m_thePos = 0;
} else {
for (int j = 0; j < 8; j++) {
m_theChaining[j] = (m_theV[j] ^ m_theV[j + 8]);
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void MixG(int msgIdx, int posA, int posB, int posC, int posD)
{
int num = msgIdx << 1;
m_theV[posA] += m_theV[posB] + m_theM[m_theIndices[num++]];
m_theV[posD] = Integers.RotateRight(m_theV[posD] ^ m_theV[posA], 16);
m_theV[posC] += m_theV[posD];
m_theV[posB] = Integers.RotateRight(m_theV[posB] ^ m_theV[posC], 12);
m_theV[posA] += m_theV[posB] + m_theM[m_theIndices[num]];
m_theV[posD] = Integers.RotateRight(m_theV[posD] ^ m_theV[posA], 8);
m_theV[posC] += m_theV[posD];
m_theV[posB] = Integers.RotateRight(m_theV[posB] ^ m_theV[posC], 7);
}
private void InitIndices()
{
for (byte b = 0; b < m_theIndices.Length; b = (byte)(b + 1)) {
m_theIndices[b] = b;
}
}
private void PermuteIndices()
{
for (byte b = 0; b < m_theIndices.Length; b = (byte)(b + 1)) {
m_theIndices[b] = SIGMA[m_theIndices[b]];
}
}
private void InitNullKey()
{
Array.Copy(IV, 0, m_theK, 0, 8);
}
private void InitKey(byte[] pKey)
{
Pack.LE_To_UInt32(pKey, 0, m_theK);
m_theMode = 16;
}
private void InitKeyFromContext()
{
Array.Copy(m_theV, 0, m_theK, 0, 8);
m_theMode = 64;
}
private void InitChunkBlock(int pDataLen, bool pFinal)
{
Array.Copy((m_theCurrBytes == 0) ? m_theK : m_theChaining, 0, m_theV, 0, 8);
Array.Copy(IV, 0, m_theV, 8, 4);
m_theV[12] = (uint)m_theCounter;
m_theV[13] = (uint)(m_theCounter >> 32);
m_theV[14] = (uint)pDataLen;
m_theV[15] = (uint)(m_theMode + ((m_theCurrBytes == 0) ? 1 : 0) + (pFinal ? 2 : 0));
m_theCurrBytes += pDataLen;
if (m_theCurrBytes >= 1024) {
IncrementBlockCount();
m_theV[15] |= 2;
}
if (pFinal && m_theStack.Count < 1)
SetRoot();
}
private void InitParentBlock()
{
Array.Copy(m_theK, 0, m_theV, 0, 8);
Array.Copy(IV, 0, m_theV, 8, 4);
m_theV[12] = 0;
m_theV[13] = 0;
m_theV[14] = 64;
m_theV[15] = (uint)(m_theMode | 4);
}
private void NextOutputBlock()
{
m_theCounter++;
Array.Copy(m_theChaining, 0, m_theV, 0, 8);
Array.Copy(IV, 0, m_theV, 8, 4);
m_theV[12] = (uint)m_theCounter;
m_theV[13] = (uint)(m_theCounter >> 32);
m_theV[14] = (uint)m_theOutputDataLen;
m_theV[15] = (uint)m_theOutputMode;
Compress();
}
private void IncrementBlockCount()
{
m_theCounter++;
m_theCurrBytes = 0;
}
private void ResetBlockCount()
{
m_theCounter = 0;
m_theCurrBytes = 0;
}
private void SetRoot()
{
m_theV[15] |= 8;
m_theOutputMode = (int)m_theV[15];
m_theOutputDataLen = (int)m_theV[14];
m_theCounter = 0;
m_outputting = true;
m_outputAvailable = -1;
Array.Copy(m_theV, 0, m_theChaining, 0, 8);
}
}
}