unit URandomHash2;

{ Copyright (c) 2019 by Herman Schoenfeld

  RandomHash2 Reference Implementation

  Distributed under the MIT software license, see the accompanying file LICENSE
  or visit http://www.opensource.org/licenses/mit-license.php.

  This unit is a part of the PascalCoin Project, an infinitely scalable
  cryptocurrency. Find us here:
  Web: https://www.pascalcoin.org
  Source: https://github.com/PascalCoin/PascalCoin

  THIS LICENSE HEADER MUST NOT BE REMOVED.
}

{%region Compiler Directives}
{$IFDEF FPC}
  {$UNDEF DELPHI}
  {$MODE delphi}
  {$DEFINE USE_UNROLLED_VARIANT}
  {$OVERFLOWCHECKS OFF}
  {$RANGECHECKS OFF}
  {$POINTERMATH ON}
  {$WARNINGS OFF}
  {$HINTS OFF}
  {$NOTES OFF}
  {$OPTIMIZATION LEVEL3}
  {$OPTIMIZATION PEEPHOLE}
  {$OPTIMIZATION REGVAR}
  {$OPTIMIZATION LOOPUNROLL}
  {$OPTIMIZATION STRENGTH}
  {$OPTIMIZATION CSE}
  {$OPTIMIZATION DFA}
  {$IFDEF CPUI386}
    {$OPTIMIZATION USEEBP}
  {$ENDIF}
  {$IFDEF CPUX86_64}
    {$OPTIMIZATION USERBP}
  {$ENDIF}
{$ELSE}
  {$DEFINE USE_UNROLLED_VARIANT}
  {$DEFINITIONINFO ON}
  {$HINTS OFF}
  {$OVERFLOWCHECKS OFF}
  {$RANGECHECKS OFF}
  {$POINTERMATH ON}
  {$STRINGCHECKS OFF}
  {$WARN DUPLICATE_CTOR_DTOR OFF}
  // 2010 only
  {$IF CompilerVersion = 21.0}
  {$DEFINE DELPHI2010}
  {$IFEND}
  // 2010 and Above
  {$IF CompilerVersion >= 21.0}
  {$DEFINE DELPHI2010_UP}
  {$IFEND}
  // XE and Above
  {$IF CompilerVersion >= 22.0}
  {$DEFINE DELPHIXE_UP}
  {$IFEND}
  // XE2 and Above
  {$IF CompilerVersion >= 23.0}
  {$DEFINE DELPHIXE2_UP}
  {$DEFINE HAS_UNITSCOPE}
  {$IFEND}
  // XE3 and Below
  {$IF CompilerVersion <= 24.0}
  {$DEFINE DELPHIXE3_DOWN}
  {$IFEND}
  // XE3 and Above
  {$IF CompilerVersion >= 24.0}
  {$DEFINE DELPHIXE3_UP}
  {$LEGACYIFEND ON}
  {$ZEROBASEDSTRINGS OFF}
  {$IFEND}
  // XE7 and Above
  {$IF CompilerVersion >= 28.0}
  {$DEFINE DELPHIXE7_UP}
  {$IFEND}
  // 10.2 Tokyo and Above
  {$IF CompilerVersion >= 32.0}
  {$DEFINE DELPHI10.2_TOKYO_UP}
  {$IFEND}
  // 2010 and Above
  {$IFNDEF DELPHI2010_UP}
  {$MESSAGE ERROR 'This Library requires Delphi 2010 or higher.'}
  {$ENDIF}
  // 10.2 Tokyo and Above
  {$IFDEF DELPHI10.2_TOKYO_UP}
  {$WARN COMBINING_SIGNED_UNSIGNED OFF}
  {$WARN COMBINING_SIGNED_UNSIGNED64 OFF}
  {$ENDIF}
{$ENDIF}
{%endregion}

interface

uses {$IFNDEF FPC}System.Generics.Collections{$ELSE}Generics.Collections{$ENDIF}, UCommon, SysUtils, HlpIHash, HlpBits, HlpHashFactory;

type

  { TRandomHash2 }

  TRandomHash2 = class sealed(TObject)
    const
      MIN_N = 2; // Min-number of hashing rounds required to compute a nonce
      MAX_N = 4; // Max-number of hashing rounds required to compute a nonce
      MIN_J = 1; // Min-number of dependent neighbouring nonces required to evaluate a nonce round
      MAX_J = 8; // Max-number of dependent neighbouring nonces required to evaluate a nonce round
      M = 64;    // The memory expansion unit (in bytes)
      NUM_HASH_ALGO = 77;
      SHA2_256_IX = 47;

    {$IFNDEF UNITTESTS}private{$ELSE}public{$ENDIF}
      FHashAlg : array[0..NUM_HASH_ALGO-1] of IHash;  // declared here to avoid race-condition during mining

      function MemTransform1(const AChunk: TBytes): TBytes; inline;
      function MemTransform2(const AChunk: TBytes): TBytes; inline;
      function MemTransform3(const AChunk: TBytes): TBytes; inline;
      function MemTransform4(const AChunk: TBytes): TBytes; inline;
      function MemTransform5(const AChunk: TBytes): TBytes; inline;
      function MemTransform6(const AChunk: TBytes): TBytes; inline;
      function MemTransform7(const AChunk: TBytes): TBytes; inline;
      function MemTransform8(const AChunk: TBytes): TBytes; inline;
      function Expand(const AInput: TBytes; AExpansionFactor: Int32; ASeed : UInt32) : TBytes;
      function Compress(const AInputs: TArray<TBytes>; ASeed : UInt32): TBytes; inline;
      function ComputeVeneerRound(const ARoundOutputs : TArray<TBytes>) : TBytes; inline;
      function CalculateRoundOutputs(const ABlockHeader: TBytes; ARound: Int32; out ARoundOutputs : TArray<TBytes>) : Boolean; overload;
    public
      constructor Create;
      destructor Destroy; override;
      function TryHash(const ABlockHeader: TBytes; AMaxRound : UInt32; out AHash : TBytes) : Boolean;
      function Hash(const ABlockHeader: TBytes): TBytes; overload; inline;
      class function Compute(const ABlockHeader: TBytes): TBytes; overload; static; inline;
  end;

 { TRandomHash2Fast }

  TRandomHash2Fast = class sealed(TObject)
    const
      MIN_N = 2; // Min-number of hashing rounds required to compute a nonce
      MAX_N = 4; // Max-number of hashing rounds required to compute a nonce
      MIN_J = 1; // Min-number of dependent neighbouring nonces required to evaluate a nonce round
      MAX_J = 8; // Max-number of dependent neighbouring nonces required to evaluate a nonce round
      M = 64;    // The memory expansion unit (in bytes)
      NUM_HASH_ALGO = 77;
      SHA2_256_IX = 47;
      public type

        TCachedHash = record
          Nonce : UInt32;
          Level : Integer;
          Header : TBytes;
          RoundOutputs : TArray<TBytes>;
        end;
        PCachedHash = ^TCachedHash;

        TCache = class
          private
            FEnablePartiallyComputed : boolean;          
            FHeaderTemplate : TBytes;
            FComputed : TList<TCachedHash>;
            FPartiallyComputed : TList<TCachedHash>;
            procedure PreProcessNewHash(const AHeader : TBytes);
          public
            constructor Create;
            destructor Destroy;
            property EnablePartiallyComputed : boolean read FEnablePartiallyComputed write FEnablePartiallyComputed;
            procedure AddPartiallyComputed(const AHeader : TBytes; ALevel : Integer; const AOutputs : TArray<TBytes>); inline;
            procedure AddFullyComputed(const AHeader : TBytes; ALevel : Integer; const AHash : TBytes); inline;
            procedure Clear;
            function HasComputedHash : Boolean; inline;
            function PopComputedHash : TCachedHash; inline;
            function HasNextPartiallyComputedHash : Boolean; inline;
            function PeekNextPartiallyComputedHash : TCachedHash; inline;
            function PopNextPartiallyComputedHash : TCachedHash; inline;
            function ComputeMemorySize : Integer;
        end;

    {$IFNDEF UNITTESTS}private{$ELSE}public{$ENDIF}
      FHashAlg : array[0..NUM_HASH_ALGO-1] of IHash;  // declared here to avoid race-condition during mining
      FCache : TCache;
      FMemStats : TStatistics;
      FCaptureMemStats : Boolean;
      FEnableCaching : Boolean;

      procedure MemTransform1(var ABuffer: TBytes; AReadStart, AWriteStart, ALength : Integer); inline;
      procedure MemTransform2(var ABuffer: TBytes; AReadStart, AWriteStart, ALength : Integer); inline;
      procedure MemTransform3(var ABuffer: TBytes; AReadStart, AWriteStart, ALength : Integer); inline;
      procedure MemTransform4(var ABuffer: TBytes; AReadStart, AWriteStart, ALength : Integer); inline;
      procedure MemTransform5(var ABuffer: TBytes; AReadStart, AWriteStart, ALength : Integer); inline;
      procedure MemTransform6(var ABuffer: TBytes; AReadStart, AWriteStart, ALength : Integer); inline;
      procedure MemTransform7(var ABuffer: TBytes; AReadStart, AWriteStart, ALength : Integer); inline;
      procedure MemTransform8(var ABuffer: TBytes; AReadStart, AWriteStart, ALength : Integer); inline;
      function Expand(const AInput: TBytes; AExpansionFactor: Int32; ASeed : UInt32) : TBytes;
      function Compress(const AInputs: TArray<TBytes>; ASeed : UInt32): TBytes;
      function ComputeVeneerRound(const ARoundOutputs : TArray<TBytes>) : TBytes;
      function CalculateRoundOutputs(const ABlockHeader: TBytes; ARound: Int32; APCachedHash : PCachedHash; out ARoundOutputs : TArray<TBytes>) : Boolean; overload;
    public
      constructor Create;
      destructor Destroy; override;
      property Cache : TCache read FCache;
      property EnableCaching : Boolean read FEnableCaching write FEnableCaching;
      property CaptureMemStats : Boolean read FCaptureMemStats write FCaptureMemStats;
      property MemStats : TStatistics read FMemStats;
      function TryHash(const ABlockHeader: TBytes; AMaxRound : UInt32; out AHash : TBytes) : Boolean;
      function Hash(const ABlockHeader: TBytes): TBytes; overload;
      function ResumeHash(const ACachedHash : TCachedHash): TBytes; overload;
      class function Compute(const ABlockHeader: TBytes): TBytes; overload; static;
  end;

 { ERandomHash2 }

  ERandomHash2 = class(Exception);

resourcestring
  SUnSupportedHash = 'Unsupported Hash Selected';
  SInvalidRound = 'Round must be between 0 and N inclusive';
  SOverlappingArgs = 'Overlapping read/write regions';
  SBufferTooSmall = 'Buffer too small to apply memory transform';
  SBlockHeaderTooSmallForNonce = 'Buffer too small to contain nonce';

implementation

uses UMemory, URandomHash;

{ TRandomHash2 }

constructor TRandomHash2.Create;
begin
  FHashAlg[0] := THashFactory.TCrypto.CreateBlake2B_160();
  FHashAlg[1] := THashFactory.TCrypto.CreateBlake2B_256();
  FHashAlg[2] := THashFactory.TCrypto.CreateBlake2B_512();
  FHashAlg[3] := THashFactory.TCrypto.CreateBlake2B_384();
  FHashAlg[4] := THashFactory.TCrypto.CreateBlake2S_128();
  FHashAlg[5] := THashFactory.TCrypto.CreateBlake2S_160();
  FHashAlg[6] := THashFactory.TCrypto.CreateBlake2S_224();
  FHashAlg[7] := THashFactory.TCrypto.CreateBlake2S_256();
  FHashAlg[8] := THashFactory.TCrypto.CreateGost();
  FHashAlg[9] := THashFactory.TCrypto.CreateGOST3411_2012_256();
  FHashAlg[10] := THashFactory.TCrypto.CreateGOST3411_2012_512();
  FHashAlg[11] := THashFactory.TCrypto.CreateGrindahl256();
  FHashAlg[12] := THashFactory.TCrypto.CreateGrindahl512();
  FHashAlg[13] := THashFactory.TCrypto.CreateHAS160();
  FHashAlg[14] := THashFactory.TCrypto.CreateHaval_3_128();
  FHashAlg[15] := THashFactory.TCrypto.CreateHaval_3_160();
  FHashAlg[16] := THashFactory.TCrypto.CreateHaval_3_192();
  FHashAlg[17] := THashFactory.TCrypto.CreateHaval_3_224();
  FHashAlg[18] := THashFactory.TCrypto.CreateHaval_3_256();
  FHashAlg[19] := THashFactory.TCrypto.CreateHaval_4_128();
  FHashAlg[20] := THashFactory.TCrypto.CreateHaval_4_160();
  FHashAlg[21] := THashFactory.TCrypto.CreateHaval_4_192();
  FHashAlg[22] := THashFactory.TCrypto.CreateHaval_4_224();
  FHashAlg[23] := THashFactory.TCrypto.CreateHaval_4_256();
  FHashAlg[24] := THashFactory.TCrypto.CreateHaval_5_128();
  FHashAlg[25] := THashFactory.TCrypto.CreateHaval_5_160();
  FHashAlg[26] := THashFactory.TCrypto.CreateHaval_5_192();
  FHashAlg[27] := THashFactory.TCrypto.CreateHaval_5_224();
  FHashAlg[28] := THashFactory.TCrypto.CreateHaval_5_256();
  FHashAlg[29] := THashFactory.TCrypto.CreateKeccak_224();
  FHashAlg[30] := THashFactory.TCrypto.CreateKeccak_256();
  FHashAlg[31] := THashFactory.TCrypto.CreateKeccak_288();
  FHashAlg[32] := THashFactory.TCrypto.CreateKeccak_384();
  FHashAlg[33] := THashFactory.TCrypto.CreateKeccak_512();
  FHashAlg[34] := THashFactory.TCrypto.CreateMD2();
  FHashAlg[35] := THashFactory.TCrypto.CreateMD5();
  FHashAlg[36] := THashFactory.TCrypto.CreateMD4();
  FHashAlg[37] := THashFactory.TCrypto.CreatePanama();
  FHashAlg[38] := THashFactory.TCrypto.CreateRadioGatun32();
  FHashAlg[39] := THashFactory.TCrypto.CreateRIPEMD();
  FHashAlg[40] := THashFactory.TCrypto.CreateRIPEMD128();
  FHashAlg[41] := THashFactory.TCrypto.CreateRIPEMD160();
  FHashAlg[42] := THashFactory.TCrypto.CreateRIPEMD256();
  FHashAlg[43] := THashFactory.TCrypto.CreateRIPEMD320();
  FHashAlg[44] := THashFactory.TCrypto.CreateSHA0();
  FHashAlg[45] := THashFactory.TCrypto.CreateSHA1();
  FHashAlg[46] := THashFactory.TCrypto.CreateSHA2_224();
  FHashAlg[47] := THashFactory.TCrypto.CreateSHA2_256();
  FHashAlg[48] := THashFactory.TCrypto.CreateSHA2_384();
  FHashAlg[49] := THashFactory.TCrypto.CreateSHA2_512();
  FHashAlg[50] := THashFactory.TCrypto.CreateSHA2_512_224();
  FHashAlg[51] := THashFactory.TCrypto.CreateSHA2_512_256();
  FHashAlg[52] := THashFactory.TCrypto.CreateSHA3_224();
  FHashAlg[53] := THashFactory.TCrypto.CreateSHA3_256();
  FHashAlg[54] := THashFactory.TCrypto.CreateSHA3_384();
  FHashAlg[55] := THashFactory.TCrypto.CreateSHA3_512();
  FHashAlg[56] := THashFactory.TCrypto.CreateSnefru_8_128();
  FHashAlg[57] := THashFactory.TCrypto.CreateSnefru_8_256();
  FHashAlg[58] := THashFactory.TCrypto.CreateTiger_3_128();
  FHashAlg[59] := THashFactory.TCrypto.CreateTiger_3_160();
  FHashAlg[60] := THashFactory.TCrypto.CreateTiger_3_192();
  FHashAlg[61] := THashFactory.TCrypto.CreateTiger_4_128();
  FHashAlg[62] := THashFactory.TCrypto.CreateTiger_4_160();
  FHashAlg[63] := THashFactory.TCrypto.CreateTiger_4_192();
  FHashAlg[64] := THashFactory.TCrypto.CreateTiger_5_128();
  FHashAlg[65] := THashFactory.TCrypto.CreateTiger_5_160();
  FHashAlg[66] := THashFactory.TCrypto.CreateTiger_5_192();
  FHashAlg[67] := THashFactory.TCrypto.CreateTiger2_3_128();
  FHashAlg[68] := THashFactory.TCrypto.CreateTiger2_3_160();
  FHashAlg[69] := THashFactory.TCrypto.CreateTiger2_3_192();
  FHashAlg[70] := THashFactory.TCrypto.CreateTiger2_4_128();
  FHashAlg[71] := THashFactory.TCrypto.CreateTiger2_4_160();
  FHashAlg[72] := THashFactory.TCrypto.CreateTiger2_4_192();
  FHashAlg[73] := THashFactory.TCrypto.CreateTiger2_5_128();
  FHashAlg[74] := THashFactory.TCrypto.CreateTiger2_5_160();
  FHashAlg[75] := THashFactory.TCrypto.CreateTiger2_5_192();
  FHashAlg[76] := THashFactory.TCrypto.CreateWhirlPool();
end;

destructor TRandomHash2.Destroy;
var i : integer;
begin
  for i := Low(FHashAlg) to High(FHashAlg) do
    FHashAlg[i] := nil;
  inherited Destroy;
end;

class function TRandomHash2.Compute(const ABlockHeader: TBytes): TBytes;
var
  LHasher : TRandomHash2;
  LDisposables : TDisposables;
begin
 LHasher := LDisposables.AddObject( TRandomHash2.Create ) as TRandomHash2;
 Result := LHasher.Hash(ABlockHeader);
end;

function TRandomHash2.TryHash(const ABlockHeader: TBytes; AMaxRound : UInt32; out AHash : TBytes) : Boolean;
var
  LOutputs: TArray<TBytes>;
  LSeed: UInt32;
begin
  if NOT CalculateRoundOutputs(ABlockHeader, AMaxRound, LOutputs) then
    Exit(False);
  AHash := ComputeVeneerRound(LOutputs);
  Result := True;
end;

function TRandomHash2.Hash(const ABlockHeader: TBytes): TBytes;
begin
  if NOT TryHash(ABlockHeader, MAX_N, Result) then
    raise ERandomHash2.Create('Internal Error: 984F52997131417E8D63C43BD686F5B2'); // Should have found final round!
end;

function TRandomHash2.ComputeVeneerRound(const ARoundOutputs : TArray<TBytes>) : TBytes;
var
  LSeed : UInt32;
begin
  LSeed := GetLastDWordLE(ARoundOutputs[High(ARoundOutputs)]);
  // Final "veneer" round of RandomHash is a SHA2-256 of compression of prior round outputs
  Result := FHashAlg[SHA2_256_IX].ComputeBytes(Compress(ARoundOutputs, LSeed)).GetBytes;
end;

function TRandomHash2.CalculateRoundOutputs(const ABlockHeader: TBytes; ARound: Int32; out ARoundOutputs : TArray<TBytes>) : Boolean;
var
  LRoundOutputs: TList<TBytes>;
  LNeighbourWasLastRound : Boolean;
  LSeed, LNumNeighbours: UInt32;
  LGen: TMersenne32;
  LRoundInput, LNeighbourNonceHeader, LOutput : TBytes;
  LParentOutputs, LNeighborOutputs, LToArray, LBuffs2: TArray<TBytes>;
  LHashFunc: IHash;
  i: Int32;
  LDisposables : TDisposables;
  LBuff : TBytes;
begin
  if (ARound < 1) or (ARound > MAX_N) then
    raise EArgumentOutOfRangeException.CreateRes(@SInvalidRound);

  LRoundOutputs := LDisposables.AddObject( TList<TBytes>.Create() ) as TList<TBytes>;
  LGen := LDisposables.AddObject( TMersenne32.Create(0) ) as TMersenne32;
  if ARound = 1 then begin
    LRoundInput := FHashAlg[SHA2_256_IX].ComputeBytes(ABlockHeader).GetBytes;
    LSeed := GetLastDWordLE( LRoundInput );
    LGen.Initialize(LSeed);
  end else begin
    if CalculateRoundOutputs(ABlockHeader, ARound - 1, LParentOutputs) = True then begin
      // Previous round was the final round, so just return it's value
      ARoundOutputs := LParentOutputs;
      Exit(True);
    end;

    // Add parent round outputs to this round outputs
    LSeed := GetLastDWordLE( LParentOutputs[High(LParentOutputs)] );
    LGen.Initialize(LSeed);
    LRoundOutputs.AddRange( LParentOutputs );

    // Add neighbouring nonce outputs to this round outputs
    LNumNeighbours := (LGen.NextUInt32 MOD (MAX_J - MIN_J)) + MIN_J;
    for i := 1 to LNumNeighbours do begin
      LNeighbourNonceHeader := SetLastDWordLE(ABlockHeader, LGen.NextUInt32); // change nonce
      LNeighbourWasLastRound := CalculateRoundOutputs(LNeighbourNonceHeader, ARound - 1, LNeighborOutputs);
      LRoundOutputs.AddRange(LNeighborOutputs);
    end;
    // Compress the parent/neighbouring outputs to form this rounds input
    LRoundInput := Compress( LRoundOutputs.ToArray, LGen.NextUInt32 );
  end;

  // Select a random hash function and hash the input to find the output
  LHashFunc := FHashAlg[LGen.NextUInt32 mod NUM_HASH_ALGO];
  LOutput := LHashFunc.ComputeBytes(LRoundInput).GetBytes;

  // Memory-expand the output, add to output list and return output list
  LOutput := Expand(LOutput, MAX_N - ARound, LGen.NextUInt32);
  LRoundOutputs.Add(LOutput);
  ARoundOutputs := LRoundOutputs.ToArray;

  // Determine if final round
  Result := (ARound = MAX_N) OR ((ARound >= MIN_N) AND (GetLastDWordLE(LOutput) MOD MAX_N = 0));
end;

function TRandomHash2.Compress(const AInputs : TArray<TBytes>; ASeed : UInt32): TBytes;
var
  i: Int32;
  LSource: TBytes;
  LGen: TMersenne32;
  LDisposables : TDisposables;
begin
  SetLength(Result, 100);
  LGen := LDisposables.AddObject( TMersenne32.Create( ASeed ) ) as TMersenne32;
  for i := 0 to 99 do
  begin
    LSource := AInputs[LGen.NextUInt32 mod Length(AInputs)];
    Result[i] := LSource[LGen.NextUInt32 mod Length(LSource)];
  end;
end;

function TRandomHash2.Expand(const AInput: TBytes; AExpansionFactor: Int32; ASeed : UInt32): TBytes;
var
  LSize, LBytesToAdd: Int32;
  LOutput, LNextChunk: TBytes;
  LRandom: UInt32;
  LGen: TMersenne32;
  LDisposables : TDisposables;
begin
  LGen := LDisposables.AddObject( TMersenne32.Create (ASeed) ) as TMersenne32;
  LSize := Length(AInput) + (AExpansionFactor * M);
  LOutput := Copy(AInput);
  LBytesToAdd := LSize - Length(AInput);

  while LBytesToAdd > 0 do
  begin
    LNextChunk := Copy(LOutput);
    if Length(LNextChunk) > LBytesToAdd then
      SetLength(LNextChunk, LBytesToAdd);

    LRandom := LGen.NextUInt32;
    case LRandom mod 8 of
      0: LOutput := ContencateBytes(LOutput, MemTransform1(LNextChunk));
      1: LOutput := ContencateBytes(LOutput, MemTransform2(LNextChunk));
      2: LOutput := ContencateBytes(LOutput, MemTransform3(LNextChunk));
      3: LOutput := ContencateBytes(LOutput, MemTransform4(LNextChunk));
      4: LOutput := ContencateBytes(LOutput, MemTransform5(LNextChunk));
      5: LOutput := ContencateBytes(LOutput, MemTransform6(LNextChunk));
      6: LOutput := ContencateBytes(LOutput, MemTransform7(LNextChunk));
      7: LOutput := ContencateBytes(LOutput, MemTransform8(LNextChunk));
    end;
    LBytesToAdd := LBytesToAdd - Length(LNextChunk);
  end;
  Result := LOutput;
end;

function TRandomHash2.MemTransform1(const AChunk: TBytes): TBytes;
var
  i, LChunkLength : UInt32;
  LState : UInt32;
begin
  // Seed XorShift32 with last byte
  LState := GetLastDWordLE(AChunk);
  if LState = 0 then
    LState := 1;

  // Select random bytes from input using XorShift32 RNG
  LChunkLength := Length(AChunk);
  SetLength(Result, LChunkLength);
  for i := 0 to High(AChunk) do
    Result[i] := AChunk[TXorShift32.Next(LState) MOD LChunkLength];
end;

function TRandomHash2.MemTransform2(const AChunk: TBytes): TBytes;
var
  i, LChunkLength, LPivot, LOdd: Int32;
begin
  LChunkLength := Length(AChunk);
  LPivot := LChunkLength SHR 1;
  LOdd := LChunkLength MOD 2;
  SetLength(Result, LChunkLength);
  Move(AChunk[LPivot + LOdd], Result[0], LPivot);
  Move(AChunk[0], Result[LPivot + LOdd], LPivot);
  // Set middle-byte for odd-length arrays
  if LOdd = 1 then
    Result[LPivot] := AChunk[LPivot];
end;

function TRandomHash2.MemTransform3(const AChunk: TBytes): TBytes;
var
  i, LChunkLength: Int32;
begin
  LChunkLength := Length(AChunk);
  SetLength(Result, LChunkLength);
  for i := 0 to High(AChunk) do
    Result[i] := AChunk[LChunkLength - i - 1];
end;

function TRandomHash2.MemTransform4(const AChunk: TBytes): TBytes;
var
  i, LChunkLength, LPivot, LOdd: Int32;
begin
  LChunkLength := Length(AChunk);
  LPivot := LChunkLength SHR 1;
  LOdd := LChunkLength MOD 2;
  SetLength(Result, LChunkLength);
  for i := 0 to Pred(LPivot) do
  begin
    Result[(i * 2)] := AChunk[i];
    Result[(i * 2) + 1] := AChunk[i + LPivot + LOdd];
  end;
  // Set final byte for odd-lengths
  if LOdd = 1 THEN
    Result[High(Result)] := AChunk[LPivot];
end;

function TRandomHash2.MemTransform5(const AChunk: TBytes): TBytes;
var
  i, LChunkLength, LPivot, LOdd: Int32;
begin
  LChunkLength := Length(AChunk);
  LPivot := LChunkLength SHR 1;
  LOdd := LChunkLength MOD 2;
  SetLength(Result, LChunkLength);
  for i := Low(AChunk) to Pred(LPivot) do
  begin
    Result[(i * 2)] := AChunk[i + LPivot + LOdd];
    Result[(i * 2) + 1] := AChunk[i];
  end;
  // Set final byte for odd-lengths
  if LOdd = 1 THEN
    Result[High(Result)] := AChunk[LPivot];
end;

function TRandomHash2.MemTransform6(const AChunk: TBytes): TBytes;
var
  i, LChunkLength, LPivot, LOdd: Int32;
begin
  LChunkLength := Length(AChunk);
  LPivot := LChunkLength SHR 1;
  LOdd := LChunkLength MOD 2;
  SetLength(Result, LChunkLength);
  for i := 0 to Pred(LPivot) do
  begin
    Result[i] := AChunk[(i * 2)] xor AChunk[(i * 2) + 1];
    Result[i + LPivot + LOdd] := AChunk[i] xor AChunk[LChunkLength - i - 1];
  end;
  // Set middle-byte for odd-lengths
  if LOdd = 1 THEN
    Result[LPivot] := AChunk[High(AChunk)];
end;

function TRandomHash2.MemTransform7(const AChunk: TBytes): TBytes;
var
  i, LChunkLength: Int32;
begin
  LChunkLength := Length(AChunk);
  SetLength(Result, LChunkLength);
  for i := 0 to High(AChunk) do
    Result[i] := TBits.RotateLeft8(AChunk[i], LChunkLength - i);
end;

function TRandomHash2.MemTransform8(const AChunk: TBytes): TBytes;
var
  i, LChunkLength: Int32;
begin
  LChunkLength := Length(AChunk);
  SetLength(Result, LChunkLength);
  for i := 0 to High(AChunk) do
    Result[i] := TBits.RotateRight8(AChunk[i], LChunkLength - i);
end;


{ TRandomHash2Fast }

constructor TRandomHash2Fast.Create;
begin
  FEnableCaching := False;
  FCache := TCache.Create;
  FHashAlg[0] := THashFactory.TCrypto.CreateBlake2B_160();
  FHashAlg[1] := THashFactory.TCrypto.CreateBlake2B_256();
  FHashAlg[2] := THashFactory.TCrypto.CreateBlake2B_512();
  FHashAlg[3] := THashFactory.TCrypto.CreateBlake2B_384();
  FHashAlg[4] := THashFactory.TCrypto.CreateBlake2S_128();
  FHashAlg[5] := THashFactory.TCrypto.CreateBlake2S_160();
  FHashAlg[6] := THashFactory.TCrypto.CreateBlake2S_224();
  FHashAlg[7] := THashFactory.TCrypto.CreateBlake2S_256();
  FHashAlg[8] := THashFactory.TCrypto.CreateGost();
  FHashAlg[9] := THashFactory.TCrypto.CreateGOST3411_2012_256();
  FHashAlg[10] := THashFactory.TCrypto.CreateGOST3411_2012_512();
  FHashAlg[11] := THashFactory.TCrypto.CreateGrindahl256();
  FHashAlg[12] := THashFactory.TCrypto.CreateGrindahl512();
  FHashAlg[13] := THashFactory.TCrypto.CreateHAS160();
  FHashAlg[14] := THashFactory.TCrypto.CreateHaval_3_128();
  FHashAlg[15] := THashFactory.TCrypto.CreateHaval_3_160();
  FHashAlg[16] := THashFactory.TCrypto.CreateHaval_3_192();
  FHashAlg[17] := THashFactory.TCrypto.CreateHaval_3_224();
  FHashAlg[18] := THashFactory.TCrypto.CreateHaval_3_256();
  FHashAlg[19] := THashFactory.TCrypto.CreateHaval_4_128();
  FHashAlg[20] := THashFactory.TCrypto.CreateHaval_4_160();
  FHashAlg[21] := THashFactory.TCrypto.CreateHaval_4_192();
  FHashAlg[22] := THashFactory.TCrypto.CreateHaval_4_224();
  FHashAlg[23] := THashFactory.TCrypto.CreateHaval_4_256();
  FHashAlg[24] := THashFactory.TCrypto.CreateHaval_5_128();
  FHashAlg[25] := THashFactory.TCrypto.CreateHaval_5_160();
  FHashAlg[26] := THashFactory.TCrypto.CreateHaval_5_192();
  FHashAlg[27] := THashFactory.TCrypto.CreateHaval_5_224();
  FHashAlg[28] := THashFactory.TCrypto.CreateHaval_5_256();
  FHashAlg[29] := THashFactory.TCrypto.CreateKeccak_224();
  FHashAlg[30] := THashFactory.TCrypto.CreateKeccak_256();
  FHashAlg[31] := THashFactory.TCrypto.CreateKeccak_288();
  FHashAlg[32] := THashFactory.TCrypto.CreateKeccak_384();
  FHashAlg[33] := THashFactory.TCrypto.CreateKeccak_512();
  FHashAlg[34] := THashFactory.TCrypto.CreateMD2();
  FHashAlg[35] := THashFactory.TCrypto.CreateMD5();
  FHashAlg[36] := THashFactory.TCrypto.CreateMD4();
  FHashAlg[37] := THashFactory.TCrypto.CreatePanama();
  FHashAlg[38] := THashFactory.TCrypto.CreateRadioGatun32();
  FHashAlg[39] := THashFactory.TCrypto.CreateRIPEMD();
  FHashAlg[40] := THashFactory.TCrypto.CreateRIPEMD128();
  FHashAlg[41] := THashFactory.TCrypto.CreateRIPEMD160();
  FHashAlg[42] := THashFactory.TCrypto.CreateRIPEMD256();
  FHashAlg[43] := THashFactory.TCrypto.CreateRIPEMD320();
  FHashAlg[44] := THashFactory.TCrypto.CreateSHA0();
  FHashAlg[45] := THashFactory.TCrypto.CreateSHA1();
  FHashAlg[46] := THashFactory.TCrypto.CreateSHA2_224();
  FHashAlg[47] := THashFactory.TCrypto.CreateSHA2_256();
  FHashAlg[48] := THashFactory.TCrypto.CreateSHA2_384();
  FHashAlg[49] := THashFactory.TCrypto.CreateSHA2_512();
  FHashAlg[50] := THashFactory.TCrypto.CreateSHA2_512_224();
  FHashAlg[51] := THashFactory.TCrypto.CreateSHA2_512_256();
  FHashAlg[52] := THashFactory.TCrypto.CreateSHA3_224();
  FHashAlg[53] := THashFactory.TCrypto.CreateSHA3_256();
  FHashAlg[54] := THashFactory.TCrypto.CreateSHA3_384();
  FHashAlg[55] := THashFactory.TCrypto.CreateSHA3_512();
  FHashAlg[56] := THashFactory.TCrypto.CreateSnefru_8_128();
  FHashAlg[57] := THashFactory.TCrypto.CreateSnefru_8_256();
  FHashAlg[58] := THashFactory.TCrypto.CreateTiger_3_128();
  FHashAlg[59] := THashFactory.TCrypto.CreateTiger_3_160();
  FHashAlg[60] := THashFactory.TCrypto.CreateTiger_3_192();
  FHashAlg[61] := THashFactory.TCrypto.CreateTiger_4_128();
  FHashAlg[62] := THashFactory.TCrypto.CreateTiger_4_160();
  FHashAlg[63] := THashFactory.TCrypto.CreateTiger_4_192();
  FHashAlg[64] := THashFactory.TCrypto.CreateTiger_5_128();
  FHashAlg[65] := THashFactory.TCrypto.CreateTiger_5_160();
  FHashAlg[66] := THashFactory.TCrypto.CreateTiger_5_192();
  FHashAlg[67] := THashFactory.TCrypto.CreateTiger2_3_128();
  FHashAlg[68] := THashFactory.TCrypto.CreateTiger2_3_160();
  FHashAlg[69] := THashFactory.TCrypto.CreateTiger2_3_192();
  FHashAlg[70] := THashFactory.TCrypto.CreateTiger2_4_128();
  FHashAlg[71] := THashFactory.TCrypto.CreateTiger2_4_160();
  FHashAlg[72] := THashFactory.TCrypto.CreateTiger2_4_192();
  FHashAlg[73] := THashFactory.TCrypto.CreateTiger2_5_128();
  FHashAlg[74] := THashFactory.TCrypto.CreateTiger2_5_160();
  FHashAlg[75] := THashFactory.TCrypto.CreateTiger2_5_192();
  FHashAlg[76] := THashFactory.TCrypto.CreateWhirlPool();
  FMemStats.Reset;
end;

destructor TRandomHash2Fast.Destroy;
var i : integer;
begin
  for i := Low(FHashAlg) to High(FHashAlg) do
    FHashAlg[i] := nil;
 FCache.Destroy;
 inherited Destroy;
end;

class function TRandomHash2Fast.Compute(const ABlockHeader: TBytes): TBytes;
var
  LHasher : TRandomHash2Fast;
  LDisposables : TDisposables;
begin
 LHasher := LDisposables.AddObject( TRandomHash2Fast.Create ) as TRandomHash2Fast;
 Result := LHasher.Hash(ABlockHeader);
end;

function TRandomHash2Fast.TryHash(const ABlockHeader: TBytes; AMaxRound : UInt32; out AHash : TBytes) : Boolean;
var
  LOutputs: TArray<TBytes>;
  LSeed: UInt32;
begin
  if NOT CalculateRoundOutputs(ABlockHeader, AMaxRound, nil, LOutputs) then
    Exit(False);
  AHash := ComputeVeneerRound(LOutputs);
  Result := True;
end;

function TRandomHash2Fast.Hash(const ABlockHeader: TBytes): TBytes;
begin
  if NOT TryHash(ABlockHeader, MAX_N, Result) then
    raise ERandomHash2.Create('Internal Error: 974F52882131417E8D63A43BD686E5B2'); // Should have found final round!
end;


function TRandomHash2Fast.ResumeHash(const ACachedHash : TCachedHash): TBytes;
var
  LOutputs: TArray<TBytes>;
  LSeed: UInt32;
begin
  if NOT CalculateRoundOutputs(ACachedHash.Header, MAX_N, @ACachedHash, LOutputs) then
    raise ERandomHash2.Create('Internal Error: 274E52882131417E8C63A43BD686E5B4'); // Should have found final round!
  Result := ComputeVeneerRound(LOutputs);
end;

function TRandomHash2Fast.ComputeVeneerRound(const ARoundOutputs : TArray<TBytes>) : TBytes;
var
  LSeed : UInt32;
  LSize, i : Integer;
begin
  LSeed := GetLastDWordLE(ARoundOutputs[High(ARoundOutputs)]);
  // Final "veneer" round of RandomHash is a SHA2-256 of compression of prior round outputs
  Result := FHashAlg[SHA2_256_IX].ComputeBytes(Compress(ARoundOutputs, LSeed)).GetBytes;
  if FCaptureMemStats then begin
    LSize := 0;
    for i :=  Low(ARoundOutputs) to High(ARoundOutputs) do
      Inc(LSize, Length(ARoundOutputs[i]));
    if FEnableCaching then
      Inc(LSize, FCache.ComputeMemorySize);
    FMemStats.AddDatum(LSize);
  end;
end;

function TRandomHash2Fast.CalculateRoundOutputs(const ABlockHeader: TBytes; ARound: Int32; APCachedHash : PCachedHash; out ARoundOutputs : TArray<TBytes>) : Boolean;
var
  LRoundOutputs: TList<TBytes>;
  LNeighbourWasLastRound : Boolean;
  LSeed, LNumNeighbours: UInt32;
  LGen: TMersenne32;
  LRoundInput, LNeighbourNonceHeader, LOutput, LXX : TBytes;
  LCachedHash : TCachedHash;
  LParentOutputs, LNeighborOutputs, LToArray, LBuffs2: TArray<TBytes>;
  LHashFunc: IHash;
  i: Int32;
  LDisposables : TDisposables;
  LBuff : TBytes;
begin
  if (ARound < 1) or (ARound > MAX_N) then
    raise EArgumentOutOfRangeException.CreateRes(@SInvalidRound);   

  if Assigned(APCachedHash) AND (APCachedHash^.Level = ARound) AND (BytesEqual(APCachedHash^.Header, ABlockHeader))  then begin
    ARoundOutputs := APCachedHash^.RoundOutputs;
    Exit(False); // assume partially evaluated 
  end;

  LRoundOutputs := LDisposables.AddObject( TList<TBytes>.Create() ) as TList<TBytes>;
  LGen := LDisposables.AddObject( TMersenne32.Create(0) ) as TMersenne32;
  if ARound = 1 then begin
    LRoundInput := FHashAlg[SHA2_256_IX].ComputeBytes(ABlockHeader).GetBytes;
    LSeed := GetLastDWordLE( LRoundInput );
    LGen.Initialize(LSeed);
  end else begin
    if CalculateRoundOutputs(ABlockHeader, ARound - 1, APCachedHash, LParentOutputs) = True then begin
      // Previous round was the final round, so just return it's value
      ARoundOutputs := LParentOutputs;
      Exit(True);
    end;

    // Add parent round outputs to this round outputs
    LSeed := GetLastDWordLE( LParentOutputs[High(LParentOutputs)] );
    LGen.Initialize(LSeed);
    LRoundOutputs.AddRange( LParentOutputs );

    // Add neighbouring nonce outputs to this round outputs
    LNumNeighbours := (LGen.NextUInt32 MOD (MAX_J - MIN_J)) + MIN_J;
    for i := 1 to LNumNeighbours do begin
      LNeighbourNonceHeader := SetLastDWordLE(ABlockHeader, LGen.NextUInt32); // change nonce
      LNeighbourWasLastRound := CalculateRoundOutputs(LNeighbourNonceHeader, ARound - 1, nil, LNeighborOutputs);
      LRoundOutputs.AddRange(LNeighborOutputs);

      // If neighbour was a fully evaluated nonce, cache it for re-use
      if FEnableCaching then 
        if LNeighbourWasLastRound then
          FCache.AddFullyComputed(LNeighbourNonceHeader, ARound - 1, ComputeVeneerRound(LNeighborOutputs) )
        else
          FCache.AddPartiallyComputed(LNeighbourNonceHeader, ARound - 1, LNeighborOutputs );
    end;
    // Compress the parent/neighbouring outputs to form this rounds input
    LRoundInput := Compress( LRoundOutputs.ToArray, LGen.NextUInt32 );
  end;

  // Select a random hash function and hash the input to find the output
  LHashFunc := FHashAlg[LGen.NextUInt32 mod NUM_HASH_ALGO];
  LOutput := LHashFunc.ComputeBytes(LRoundInput).GetBytes;

  // Memory-expand the output, add to output list and return output list
  LOutput := Expand(LOutput, MAX_N - ARound, LGen.NextUInt32);
  LRoundOutputs.Add(LOutput);
  ARoundOutputs := LRoundOutputs.ToArray;

  // Determine if final round
  Result := (ARound = MAX_N) OR ((ARound >= MIN_N) AND (GetLastDWordLE(LOutput) MOD MAX_N = 0));
end;

function TRandomHash2Fast.Compress(const AInputs : TArray<TBytes>; ASeed : UInt32): TBytes;
var
  i: Int32;
  LSource: TBytes;
  LGen: TMersenne32;
  LDisposables : TDisposables;
begin
  SetLength(Result, 100);
  LGen := LDisposables.AddObject( TMersenne32.Create( ASeed ) ) as TMersenne32;
  for i := 0 to 99 do
  begin
    LSource := AInputs[LGen.NextUInt32 mod Length(AInputs)];
    Result[i] := LSource[LGen.NextUInt32 mod Length(LSource)];
  end;
end;

function TRandomHash2Fast.Expand(const AInput: TBytes; AExpansionFactor: Int32; ASeed : UInt32): TBytes;
var
  LOutput: TBytes;
  LReadEnd, LCopyLen, LInputSize : UInt32;
  LGen: TMersenne32;
  LDisposables : TDisposables;
begin
  LInputSize := Length (AInput);
  LGen := LDisposables.AddObject( TMersenne32.Create ( ASeed ) ) as TMersenne32;
  SetLength(LOutput, LInputSize + (AExpansionFactor * M));

  // Copy the genesis blob
  Move(AInput[0], LOutput[0], LInputSize);
  LReadEnd := LInputSize - 1;
  LCopyLen := LInputSize;

  while LReadEnd < Pred(Length(LOutput)) do
  begin
    if (LReadEnd + 1 + LCopyLen) > Length(LOutput) then
      LCopyLen := Length(LOutput) - (LReadEnd + 1);
    case LGen.NextUInt32 mod 8 of
      0: MemTransform1(LOutput, 0, LReadEnd+1, LCopyLen);
      1: MemTransform2(LOutput, 0, LReadEnd+1, LCopyLen);
      2: MemTransform3(LOutput, 0, LReadEnd+1, LCopyLen);
      3: MemTransform4(LOutput, 0, LReadEnd+1, LCopyLen);
      4: MemTransform5(LOutput, 0, LReadEnd+1, LCopyLen);
      5: MemTransform6(LOutput, 0, LReadEnd+1, LCopyLen);
      6: MemTransform7(LOutput, 0, LReadEnd+1, LCopyLen);
      7: MemTransform8(LOutput, 0, LReadEnd+1, LCopyLen);
    end;
    Inc(LReadEnd, LCopyLen);
    Inc(LCopyLen, LCopyLen);
  end;
  Result := LOutput;
end;

procedure TRandomHash2Fast.MemTransform1(var ABuffer: TBytes; AReadStart, AWriteStart, ALength : Integer);
var
  i, LReadEnd, LWriteEnd : UInt32;
  LState : UInt32;
begin
  LReadEnd := AReadStart + ALength - 1;
  LWriteEnd := AWriteStart + ALength - 1;
  if LReadEnd >= AWriteStart then
    raise EArgumentOutOfRangeException.Create(SOverlappingArgs);

  // Seed XorShift32 with last byte
  LState := GetDWordLE(ABuffer, LReadEnd-3);
  if LState = 0 then
    LState := 1;

  // Select random bytes from input using XorShift32 RNG
  for i := AWriteStart to LWriteEnd do
    ABuffer[i] := ABuffer[AReadStart + (TXorShift32.Next(LState) MOD ALength)];
end;

procedure TRandomHash2Fast.MemTransform2(var ABuffer: TBytes; AReadStart, AWriteStart, ALength : Integer);
var
  i, LReadEnd, LWriteEnd, LPivot, LOdd: Int32;
begin
  LReadEnd := AReadStart + ALength - 1;
  LWriteEnd := AWriteStart + ALength - 1;
  if LReadEnd >= AWriteStart then
    raise EArgumentOutOfRangeException.Create(SOverlappingArgs);
  if LWriteEnd >= Length(ABuffer) then
    raise EArgumentOutOfRangeException.Create(SBufferTooSmall);

  LPivot := ALength SHR 1;
  LOdd := ALength MOD 2;
  Move(ABuffer[AReadStart + LPivot + LOdd], ABuffer[AWriteStart], LPivot);
  Move(ABuffer[AReadStart], ABuffer[AWriteStart + LPivot + LOdd], LPivot);
  // Set middle-byte for odd-length arrays
  if LOdd = 1 then
    ABuffer[AWriteStart + LPivot] := ABuffer[AReadStart + LPivot];
end;

procedure TRandomHash2Fast.MemTransform3(var ABuffer: TBytes; AReadStart, AWriteStart, ALength : Integer);
var
  i, LReadEnd, LWriteEnd: Int32;
  LReadPtr, LWritePtr : PByte;
begin
  LReadEnd := AReadStart + ALength - 1;
  LWriteEnd := AWriteStart + ALength - 1;
  if LReadEnd >= AWriteStart then
    raise EArgumentOutOfRangeException.Create(SOverlappingArgs);
  if LWriteEnd >= Length(ABuffer) then
    raise EArgumentOutOfRangeException.Create(SBufferTooSmall);

  LReadPtr := PByte(ABuffer) + AReadStart;
  LWritePtr := PByte(ABuffer) + LWriteEnd;
  for i := 0 to Pred(ALength) do begin
    LWritePtr^ := LReadPtr^;
    Inc(LReadPtr);
    Dec(LWritePtr);
  end;
end;

procedure TRandomHash2Fast.MemTransform4(var ABuffer: TBytes; AReadStart, AWriteStart, ALength : Integer);
var
  i, LReadEnd, LWriteEnd, LPivot, LOdd: Int32;
begin
  LReadEnd := AReadStart + ALength - 1;
  LWriteEnd := AWriteStart + ALength - 1;
  if LReadEnd >= AWriteStart then
    raise EArgumentOutOfRangeException.Create(SOverlappingArgs);
  if LWriteEnd >= Length(ABuffer) then
    raise EArgumentOutOfRangeException.Create(SBufferTooSmall);

  LPivot := ALength SHR 1;
  LOdd := ALength MOD 2;
  for i := 0 to Pred(LPivot) do
  begin
    ABuffer[AWriteStart + (i * 2)] := ABuffer[AReadStart + i];
    ABuffer[AWriteStart + (i * 2) + 1] := ABuffer[AReadStart + i + LPivot + LOdd];
  end;
  // Set final byte for odd-lengths
  if LOdd = 1 THEN
    ABuffer[LWriteEnd] := ABuffer[AReadStart + LPivot];
end;

procedure TRandomHash2Fast.MemTransform5(var ABuffer: TBytes; AReadStart, AWriteStart, ALength : Integer);
var
  i, LReadEnd, LWriteEnd, LPivot, LOdd: Int32;
begin
  LReadEnd := AReadStart + ALength - 1;
  LWriteEnd := AWriteStart + ALength - 1;
  if LReadEnd >= AWriteStart then
    raise EArgumentOutOfRangeException.Create(SOverlappingArgs);
  if LWriteEnd >= Length(ABuffer) then
    raise EArgumentOutOfRangeException.Create(SBufferTooSmall);

  LPivot := ALength SHR 1;
  LOdd := ALength MOD 2;
  for i := 0 to Pred(LPivot) do
  begin
    ABuffer[AWriteStart + (i * 2)] := ABuffer[AReadStart + i + LPivot + LOdd];
    ABuffer[AWriteStart + (i * 2) + 1] := ABuffer[AReadStart + i];
  end;
  // Set final byte for odd-lengths
  if LOdd = 1 THEN
    ABuffer[LWriteEnd] := ABuffer[AReadStart + LPivot];
end;

procedure TRandomHash2Fast.MemTransform6(var ABuffer: TBytes; AReadStart, AWriteStart, ALength : Integer);
var
  i, LReadEnd, LWriteEnd, LPivot, LOdd: Int32;
begin
  LReadEnd := AReadStart + ALength - 1;
  LWriteEnd := AWriteStart + ALength - 1;
  if LReadEnd >= AWriteStart then
    raise EArgumentOutOfRangeException.Create(SOverlappingArgs);
  if LWriteEnd >= Length(ABuffer) then
    raise EArgumentOutOfRangeException.Create(SBufferTooSmall);

  LPivot := ALength SHR 1;
  LOdd := ALength MOD 2;
  for i := 0 to Pred(LPivot) do
  begin
    ABuffer[AWriteStart + i] := ABuffer[AReadStart + (i * 2)] xor ABuffer[AReadStart + (i * 2) + 1];
    ABuffer[AWriteStart + i + LPivot + LOdd] := ABuffer[AReadStart + i] xor ABuffer[AReadStart + ALength - i - 1];
  end;
  // Set middle-byte for odd-lengths
  if LOdd = 1 THEN
    ABuffer[AWriteStart + LPivot] := ABuffer[AReadStart + ALength - 1];
end;

procedure TRandomHash2Fast.MemTransform7(var ABuffer: TBytes; AReadStart, AWriteStart, ALength : Integer);
var
  i, LReadEnd, LWriteEnd: Int32;
begin
  LReadEnd := AReadStart + ALength - 1;
  LWriteEnd := AWriteStart + ALength - 1;
  if LReadEnd >= AWriteStart then
    raise EArgumentOutOfRangeException.Create(SOverlappingArgs);
  if LWriteEnd >= Length(ABuffer) then
    raise EArgumentOutOfRangeException.Create(SBufferTooSmall);

  for i := 0 to Pred(ALength) do
    ABuffer[AWriteStart + i] := TBits.RotateLeft8(ABuffer[AReadStart + i], ALength - i);
end;

procedure TRandomHash2Fast.MemTransform8(var ABuffer: TBytes; AReadStart, AWriteStart, ALength : Integer);
var
  i, LChunkLength, LReadEnd, LWriteEnd: Int32;
begin
  LReadEnd := AReadStart + ALength - 1;
  LWriteEnd := AWriteStart + ALength - 1;
  if LReadEnd >= AWriteStart then
    raise EArgumentOutOfRangeException.Create(SOverlappingArgs);
  if LWriteEnd >= Length(ABuffer) then
    raise EArgumentOutOfRangeException.Create(SBufferTooSmall);

  for i := 0 to Pred(ALength) do
    ABuffer[AWriteStart + i] := TBits.RotateRight8(ABuffer[AReadStart + i], ALength - i);
end;


{ TRandomHash2Fast.TCache }

constructor TRandomHash2Fast.TCache.Create;
var i : Integer;
begin
  FEnablePartiallyComputed := false;
  FComputed := TList<TCachedHash>.Create;
  FComputed.Capacity := 100;
  SetLength(Self.FHeaderTemplate, 0);
  FPartiallyComputed := TList<TCachedHash>.Create;
  FPartiallyComputed.Capacity := 1000;
end;

destructor TRandomHash2Fast.TCache.Destroy;
var i : Integer;
begin
  FComputed.Clear;
  FreeAndNil(FComputed);
  FPartiallyComputed.Clear;
  FreeAndNil(FPartiallyComputed);
end;

procedure TRandomHash2Fast.TCache.AddPartiallyComputed(const AHeader : TBytes; ALevel : Integer; const AOutputs : TArray<TBytes>);
var
  LItem : TCachedHash;
begin
  PreProcessNewHash(AHeader);
  if NOT FEnablePartiallyComputed then
    Exit;

  // Only keep 10 level 3's partially calculated
  if (ALevel < 3) OR (FPartiallyComputed.Count > 10) then Exit;
    
  LItem.Nonce := GetLastDWordLE(AHeader);
  LItem.Header := AHeader;  
  LItem.Level := ALevel;
  LItem.RoundOutputs := AOutputs;
  FPartiallyComputed.Add(LItem);
end;

procedure TRandomHash2Fast.TCache.AddFullyComputed(const AHeader : TBytes; ALevel : Integer; const AHash : TBytes);
var
  LItem : TCachedHash;
begin
  PreProcessNewHash(AHeader);
  LItem.Nonce := GetLastDWordLE(AHeader);
  LItem.Header := AHeader;
  LItem.Level := ALevel;
  LItem.RoundOutputs := TArray<TBytes>.Create( AHash );
  FComputed.Add(LItem);
end;

procedure TRandomHash2Fast.TCache.PreProcessNewHash(const AHeader : TBytes);
var i : integer;
begin
  // if header is a new template, flush cache
  if NOT BytesEqual(FHeaderTemplate, AHeader, 0, 32 - 4) then begin
    Clear;
    FHeaderTemplate := SetLastDWordLE(AHeader, 0);
  end;  
end;

procedure TRandomHash2Fast.TCache.Clear;
begin
  FComputed.Clear;
  FComputed.Capacity := 100;
  FPartiallyComputed.Clear;
  FPartiallyComputed.Capacity := 1000;
  SetLength(FHeaderTemplate, 0);
end;

function TRandomHash2Fast.TCache.HasComputedHash : Boolean;
begin
  Result := FComputed.Count > 0;
end;

function TRandomHash2Fast.TCache.PopComputedHash : TCachedHash;
begin
  Result := FComputed.Last;
  FComputed.Delete(FComputed.Count - 1);
end;

function TRandomHash2Fast.TCache.HasNextPartiallyComputedHash : boolean;
begin
  Result := FPartiallyComputed.Count > 0;
end;

function TRandomHash2Fast.TCache.PeekNextPartiallyComputedHash : TCachedHash;
var i : Integer;
begin
  if FPartiallyComputed.Count > 0 then begin
    Result := FPartiallyComputed[0];
    Exit;
  end;
  raise Exception.Create('Cache is empty');
end;

function TRandomHash2Fast.TCache.PopNextPartiallyComputedHash : TCachedHash;
var 
  i : Integer;
  LList : TList<TCachedHash>;
begin
  if FPartiallyComputed.Count > 0 then begin
    Result := FPartiallyComputed[0];
    FPartiallyComputed.Delete(0);
    Exit;
  end;
  raise Exception.Create('Cache is empty');
end;

function TRandomHash2Fast.TCache.ComputeMemorySize : Integer;
var 
  i,j,k : Integer;
  LList : TList<TCachedHash>;
  LItem : TCachedHash;
begin
  Result := Length(FHeaderTemplate);
  Inc(Result, FComputed.Count * (4 + 4 + 32));
  for j := 0 to FPartiallyComputed.Count - 1 do begin
    LItem := FPartiallyComputed[j];
    Inc(Result, 4);
    Inc(Result, 4);
    Inc(Result, 32);            
    for k := Low(LItem.RoundOutputs) to High(LItem.RoundOutputs) do
      Inc(Result, Length(LItem.RoundOutputs[k])); 
  end;
end;


end.
