Przeglądaj źródła

PIP-0037: RandomHash2 algorithm strengthening + optimization (#5)

- Changed MIN_J to 1
- Added TRandomHash2Fast implementation
- Added more unit test coverage
- Added significant optimizations
Herman Schoenfeld 6 lat temu
rodzic
commit
629de00bb7

+ 16 - 17
src/core/UCrypto.pas

@@ -88,11 +88,10 @@ Type
     class procedure DoDoubleSha256(p : PAnsiChar; plength : Cardinal; out ResultSha256 : TRawBytes); overload;
     class function DoRandomHash(const TheMessage : TRawBytes) : TRawBytes; overload;
     class function DoRandomHash2(const TheMessage : TRawBytes) : TRawBytes; overload;
-    class procedure DoRandomHash(p : PAnsiChar; plength : Cardinal; out ResultSha256 : TRawBytes); overload;
-    class procedure DoRandomHash2(p : PAnsiChar; plength : Cardinal; out ResultSha256 : TRawBytes); overload;
-    class procedure DoRandomHash(AFastHasher : TRandomHashFast; p : PAnsiChar; plength : Cardinal; out ResultSha256 : TRawBytes); overload;
-    class procedure DoRandomHash2(AHasher : TRandomHash2; p : PAnsiChar; plength : Cardinal; out ResultSha256 : TRawBytes); overload;
-   // class procedure DoRandomHash2(AHasher : TRandomHash2; p : PAnsiChar; plength : Cardinal; out ResultSha256 : TRawBytes); overload;
+    class procedure DoRandomHash(p : PAnsiChar; plength : Cardinal; out AResult : TRawBytes); overload;
+    class procedure DoRandomHash2(p : PAnsiChar; plength : Cardinal; out AResult : TRawBytes); overload;
+    class procedure DoRandomHash(AFastHasher : TRandomHashFast; p : PAnsiChar; plength : Cardinal; out AResult : TRawBytes); overload;
+    class procedure DoRandomHash2(AHasher : TRandomHash2Fast; p : PAnsiChar; plength : Cardinal; out AResult : TRawBytes); overload;
     class function DoRipeMD160_HEXASTRING(const TheMessage : TRawBytes) : TRawBytes; overload;
     class function DoRipeMD160AsRaw(p : PAnsiChar; plength : Cardinal) : TRawBytes; overload;
     class function DoRipeMD160AsRaw(const TheMessage : TRawBytes) : TRawBytes; overload;
@@ -778,52 +777,52 @@ begin
   Result := TRandomHash2.Compute(TheMessage);
 end;
 
-class procedure TCrypto.DoRandomHash(p : PAnsiChar; plength : Cardinal; out ResultSha256 : TRawBytes);
+class procedure TCrypto.DoRandomHash(p : PAnsiChar; plength : Cardinal; out AResult : TRawBytes);
 var
   LInput : TBytes;
   LResult : TBytes;
 begin
-  if Length(ResultSha256) <> 32 then SetLength(ResultSha256, 32);
+  if Length(AResult) <> 32 then SetLength(AResult, 32);
   SetLength(LInput, plength);
   Move(p^, LInput[0], plength);
   LResult := TRandomHashFast.Compute(LInput);
-  Move(LResult[0], ResultSha256[Low(ResultSha256)], 32);
+  Move(LResult[0], AResult[Low(AResult)], 32);
 end;
 
-class procedure TCrypto.DoRandomHash2(p : PAnsiChar; plength : Cardinal; out ResultSha256 : TRawBytes);
+class procedure TCrypto.DoRandomHash2(p : PAnsiChar; plength : Cardinal; out AResult : TRawBytes);
 var
   LInput : TBytes;
   LResult : TBytes;
 begin
-  if Length(ResultSha256) <> 32 then SetLength(ResultSha256, 32);
+  if Length(AResult) <> 32 then SetLength(AResult, 32);
   SetLength(LInput, plength);
   Move(p^, LInput[0], plength);
   LResult := TRandomHash2.Compute(LInput);
-  Move(LResult[0], ResultSha256[Low(ResultSha256)], 32);
+  Move(LResult[0], AResult[Low(AResult)], 32);
 end;
 
-class procedure TCrypto.DoRandomHash(AFastHasher : TRandomHashFast; p : PAnsiChar; plength : Cardinal; out ResultSha256 : TRawBytes);
+class procedure TCrypto.DoRandomHash(AFastHasher : TRandomHashFast; p : PAnsiChar; plength : Cardinal; out AResult : TRawBytes);
 var
   LInput : TBytes;
   LResult : TBytes;
 begin
-  if Length(ResultSha256) <> 32 then SetLength(ResultSha256, 32);
+  if Length(AResult) <> 32 then SetLength(AResult, 32);
   SetLength(LInput, plength);
   Move(p^, LInput[0], plength);
   LResult := AFastHasher.Hash(LInput);
-  Move(LResult[0], ResultSha256[Low(ResultSha256)], 32);
+  Move(LResult[0], AResult[Low(AResult)], 32);
 end;
 
-class procedure TCrypto.DoRandomHash2(AHasher : TRandomHash2; p : PAnsiChar; plength : Cardinal; out ResultSha256 : TRawBytes);
+class procedure TCrypto.DoRandomHash2(AHasher : TRandomHash2Fast; p : PAnsiChar; plength : Cardinal; out AResult : TRawBytes);
 var
   LInput : TBytes;
   LResult : TBytes;
 begin
-  if Length(ResultSha256) <> 32 then SetLength(ResultSha256, 32);
+  if Length(AResult) <> 32 then SetLength(AResult, 32);
   SetLength(LInput, plength);
   Move(p^, LInput[0], plength);
   LResult := AHasher.Hash(LInput);
-  Move(LResult[0], ResultSha256[Low(ResultSha256)], 32);
+  Move(LResult[0], AResult[Low(AResult)], 32);
 end;
 
 { TBigNum }

+ 4 - 3
src/core/UPoolMinerThreads.pas

@@ -804,8 +804,8 @@ Var
   dstep : Integer;
   LUseRandomHash : boolean;
   LRandomHasher : TRandomHashFast;
-  LRandomHasher2 : TRandomHash2;
-  LCachedItem : TRandomHash2.TCachedHash;
+  LRandomHasher2 : TRandomHash2Fast;
+  LCachedItem : TRandomHash2Fast.TCachedHash;
   LNonceResult : TNonceResult;
   LResultsToCheck : TList<TNonceResult>;
   LDisposables : TDisposables;
@@ -815,8 +815,9 @@ begin
   nonce := 0;
   dstep := 0;
   LRandomHasher := LDisposables.AddObject( TRandomHashFast.Create ) as TRandomHashFast;
-  LRandomHasher2 := LDisposables.AddObject( TRandomHash2.Create ) as TRandomHash2;
+  LRandomHasher2 := LDisposables.AddObject( TRandomHash2Fast.Create ) as TRandomHash2Fast;
   LResultsToCheck := LDisposables.AddObject( TList<TNonceResult>.Create ) as TList<TNonceResult>;
+  LRandomHasher2.EnableCaching := True;
   Try
     while (Not Terminated) And (Not FCPUDeviceThread.Terminated) do begin
       Try

+ 13 - 13
src/core/URandomHash.pas

@@ -117,7 +117,7 @@ type
       M = (10 * 1024) * 5; // The memory expansion unit (in bytes), total bytes per nonce = M * (2^N (N-2) + 2)
 
     {$IFNDEF UNITTESTS}private{$ELSE}public{$ENDIF}
-      FMurmurHash3_x86_32 : IHash;
+      FMurmur3 : IHash;
       FHashAlg : array[0..17] of IHash;  // declared here to avoid race-condition during mining
       function ContencateByteArrays(const AChunk1, AChunk2: TBytes): TBytes; inline;
       function MemTransform1(const AChunk: TBytes): TBytes; inline;
@@ -185,7 +185,7 @@ type
     private
       function GetCachedHeader : TBytes;
     {$IFNDEF UNITTESTS}private{$ELSE}public{$ENDIF}
-      FMurmurHash3_x86_32 : IHash;
+      FMurmur3 : IHash;
       FHashAlg : array[0..17] of IHash;  // declared here to avoid race-condition during mining
       FCachedHeader : TBytes;
       FCachedNonce : UInt32;
@@ -287,7 +287,7 @@ end;
 
 constructor TRandomHash.Create;
 begin
-  FMurmurHash3_x86_32 := THashFactory.THash32.CreateMurmurHash3_x86_32();
+  FMurmur3 := THashFactory.THash32.CreateMurmurHash3_x86_32();
   FHashAlg[0] := THashFactory.TCrypto.CreateSHA2_256();
   FHashAlg[1] := THashFactory.TCrypto.CreateSHA2_384();
   FHashAlg[2] := THashFactory.TCrypto.CreateSHA2_512();
@@ -311,7 +311,7 @@ end;
 destructor TRandomHash.Destroy;
 var i : integer;
 begin
- FMurmurHash3_x86_32 := nil;
+ FMurmur3 := nil;
  for i := Low(FHashAlg) to High(FHashAlg) do
    FHashAlg[i] := nil;
  inherited Destroy;
@@ -397,19 +397,19 @@ end;
 
 function TRandomHash.Checksum(const AInput: TBytes): UInt32;
 begin
-  Result := FMurmurHash3_x86_32.ComputeBytes(AInput).GetUInt32;
+  Result := FMurmur3.ComputeBytes(AInput).GetUInt32;
 end;
 
 function TRandomHash.Checksum(const AInput : TArray<TBytes>): UInt32;
 var
   i: Int32;
 begin
-  FMurmurHash3_x86_32.Initialize;
+  FMurmur3.Initialize;
   for i := Low(AInput) to High(AInput) do
   begin
-    FMurmurHash3_x86_32.TransformBytes(AInput[i]);
+    FMurmur3.TransformBytes(AInput[i]);
   end;
-  Result := FMurmurHash3_x86_32.TransformFinal.GetUInt32;
+  Result := FMurmur3.TransformFinal.GetUInt32;
 end;
 
 function TRandomHash.Compress(const AInputs : TArray<TBytes>): TBytes;
@@ -593,7 +593,7 @@ end;
 
 constructor TRandomHashFast.Create;
 begin
-  FMurmurHash3_x86_32 := THashFactory.THash32.CreateMurmurHash3_x86_32();
+  FMurmur3 := THashFactory.THash32.CreateMurmurHash3_x86_32();
   FHashAlg[0] := THashFactory.TCrypto.CreateSHA2_256();
   FHashAlg[1] := THashFactory.TCrypto.CreateSHA2_384();
   FHashAlg[2] := THashFactory.TCrypto.CreateSHA2_512();
@@ -620,7 +620,7 @@ end;
 destructor TRandomHashFast.Destroy;
 var i : integer;
 begin
- FMurmurHash3_x86_32 := nil;
+ FMurmur3 := nil;
  for i := Low(FHashAlg) to High(FHashAlg) do
    FHashAlg[i] := nil;
  if Assigned(FCachedOutput) then
@@ -760,9 +760,9 @@ end;
 
 function TRandomHashFast.Checksum(const AInput: TBytes; AOffset, ALength: Integer): UInt32;
 begin
-  FMurmurHash3_x86_32.Initialize;
-  FMurmurHash3_x86_32.TransformBytes(AInput, AOffset, ALength);
-  Result := FMurmurHash3_x86_32.TransformFinal.GetUInt32();
+  FMurmur3.Initialize;
+  FMurmur3.TransformBytes(AInput, AOffset, ALength);
+  Result := FMurmur3.TransformFinal.GetUInt32();
 end;
 
 function TRandomHashFast.Compress(const AInputs : TChecksummedByteCollection): TBytes;

+ 482 - 131
src/core/URandomHash2.pas

@@ -105,11 +105,49 @@ type
 
   TRandomHash2 = class sealed(TObject)
     const
-      MIN_N = 2; // Min-number of hashing rounds required to compute a nonce, min total rounds = J^MIN_N
-      MAX_N = 4; // Max-number of hashing rounds required to compute a nonce, max total rounds = J^MAX_N
-      MIN_J = 0; // Min-number of dependent neighbouring nonces required to evaluate a nonce round
+      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 = 256;    // The memory expansion unit (in bytes), max total bytes per nonce = M * ((MAX_J+1)^MAX_N (MAX_N-2) + 2)
+      M = 64;    // The memory expansion unit (in bytes)
+      NUM_HASH_ALGO = 18;
+
+    {$IFNDEF UNITTESTS}private{$ELSE}public{$ENDIF}
+      FHashAlg : array[0..17] of IHash;  // declared here to avoid race-condition during mining
+      FMemStats : TStatistics;
+      FCaptureMemStats : Boolean;
+
+      function ContencateByteArrays(const AChunk1, AChunk2: TBytes): TBytes; inline;
+      function MemTransform1(const AChunk: TBytes): TBytes;
+      function MemTransform2(const AChunk: TBytes): TBytes;
+      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;
+      property CaptureMemStats : Boolean read FCaptureMemStats write FCaptureMemStats;
+      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 = 18;
 
       public type
@@ -120,32 +158,33 @@ type
           Hash : TBytes;
         end;
 
-      {$IFNDEF UNITTESTS}private{$ELSE}public{$ENDIF}
-      FMurmurHash3_x86_32 : IHash;
+    {$IFNDEF UNITTESTS}private{$ELSE}public{$ENDIF}
       FHashAlg : array[0..17] of IHash;  // declared here to avoid race-condition during mining
       FCachedHeaderTemplate : TBytes;
       FCachedHashes : TList<TCachedHash>;
       FMemStats : TStatistics;
+      FCaptureMemStats : Boolean;
+      FEnableCaching : Boolean;
 
       function GetCachedHashes : TArray<TCachedHash>; inline;
       function ContencateByteArrays(const AChunk1, AChunk2: TBytes): TBytes; inline;
-      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;
+      procedure MemTransform1(var ABuffer: TBytes; AReadStart, AWriteStart, ALength : Integer);
+      procedure MemTransform2(var ABuffer: TBytes; AReadStart, AWriteStart, ALength : Integer);
+      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; inline;
-      function SetLastDWordLE(const ABytes: TBytes; AValue: UInt32): TBytes; inline;
-      function GetLastDWordLE(const ABytes: TBytes) : UInt32; inline;
-      function ComputeVeneerRound(const ARoundOutputs : TArray<TBytes>) : TBytes; inline;
+      function ComputeVeneerRound(const ARoundOutputs : TArray<TBytes>) : TBytes;
       function CalculateRoundOutputs(const ABlockHeader: TBytes; ARound: Int32; out ARoundOutputs : TArray<TBytes>) : Boolean; overload;
     public
       constructor Create;
       destructor Destroy; override;
+      property EnableCaching : Boolean read FEnableCaching write FEnableCaching;
+      property CaptureMemStats : Boolean read FCaptureMemStats write FCaptureMemStats;
       property CachedHashes : TArray<TCachedHash> read GetCachedHashes;
       property MemStats : TStatistics read FMemStats;
       function HasCachedHash : Boolean; inline;
@@ -175,9 +214,6 @@ uses UMemory, URandomHash;
 
 constructor TRandomHash2.Create;
 begin
-  FMurmurHash3_x86_32 := THashFactory.THash32.CreateMurmurHash3_x86_32();
-  SetLength(Self.FCachedHeaderTemplate, 0);
-  FCachedHashes := TList<TCachedHash>.Create;
   FHashAlg[0] := THashFactory.TCrypto.CreateSHA2_256();
   FHashAlg[1] := THashFactory.TCrypto.CreateSHA2_384();
   FHashAlg[2] := THashFactory.TCrypto.CreateSHA2_512();
@@ -202,12 +238,9 @@ end;
 destructor TRandomHash2.Destroy;
 var i : integer;
 begin
- FCachedHashes.Clear;
- FreeAndNil(FCachedHashes);
- FMurmurHash3_x86_32 := nil;
- for i := Low(FHashAlg) to High(FHashAlg) do
-   FHashAlg[i] := nil;
- inherited Destroy;
+  for i := Low(FHashAlg) to High(FHashAlg) do
+    FHashAlg[i] := nil;
+  inherited Destroy;
 end;
 
 class function TRandomHash2.Compute(const ABlockHeader: TBytes): TBytes;
@@ -239,22 +272,10 @@ end;
 function TRandomHash2.ComputeVeneerRound(const ARoundOutputs : TArray<TBytes>) : TBytes;
 var
   LSeed : UInt32;
-  LSize : UInt32;
-  i : integer;
-  LCachedItem : TCachedHash;
 begin
   LSeed := GetLastDWordLE(ARoundOutputs[High(ARoundOutputs)]);
   // Final "veneer" round of RandomHash is a SHA2-256 of compression of prior round outputs
   Result := FHashAlg[0].ComputeBytes(Compress(ARoundOutputs, LSeed)).GetBytes;
-  // Tally memstats
-  LSize := 0;
-  for i := Low(ARoundOutputs) to High(ARoundOutputs) do
-    Inc(LSize, Length(ARoundOutputs[i]));
-  for i := 0 to FCachedHashes.Count - 1 do begin
-    LCachedItem := FCachedHashes.Items[i];
-    Inc(LSize, Length(LCachedItem.Hash) + Length(LCachedItem.Header) + 4);
-  end;
-  FMemStats.AddDatum(LSize);
 end;
 
 function TRandomHash2.CalculateRoundOutputs(const ABlockHeader: TBytes; ARound: Int32; out ARoundOutputs : TArray<TBytes>) : Boolean;
@@ -264,7 +285,6 @@ var
   LSeed, LNumNeighbours: UInt32;
   LGen: TMersenne32;
   LRoundInput, LNeighbourNonceHeader, LOutput : TBytes;
-  LCachedHash : TCachedHash;
   LParentOutputs, LNeighborOutputs, LToArray, LBuffs2: TArray<TBytes>;
   LHashFunc: IHash;
   i: Int32;
@@ -298,19 +318,6 @@ begin
       LNeighbourNonceHeader := SetLastDWordLE(ABlockHeader, LGen.NextUInt32); // change nonce
       LNeighbourWasLastRound := CalculateRoundOutputs(LNeighbourNonceHeader, ARound - 1, LNeighborOutputs);
       LRoundOutputs.AddRange(LNeighborOutputs);
-
-      // If neighbour was a fully evaluated nonce, cache it for re-use
-      if LNeighbourWasLastRound then begin
-        LCachedHash.Nonce := GetLastDWordLE(LNeighbourNonceHeader);
-        LCachedHash.Header := LNeighbourNonceHeader;
-        LCachedHash.Hash := ComputeVeneerRound(LNeighborOutputs);
-        // if header is different (other than nonce), clear cache
-        if NOT BytesEqual(FCachedHeaderTemplate, LCachedHash.Header, 0, 32 - 4) then begin
-          FCachedHashes.Clear;
-          FCachedHeaderTemplate := SetLastDWordLE(LCachedHash.Header, 0);
-        end;
-        FCachedHashes.Add(LCachedHash);
-      end;
     end;
     // Compress the parent/neighbouring outputs to form this rounds input
     LRoundInput := Compress( LRoundOutputs.ToArray, LGen.NextUInt32 );
@@ -329,39 +336,6 @@ begin
   Result := (ARound = MAX_N) OR ((ARound >= MIN_N) AND (GetLastDWordLE(LOutput) MOD MAX_N = 0));
 end;
 
-function TRandomHash2.SetLastDWordLE(const ABytes: TBytes; AValue: UInt32): TBytes;
-var
-  ABytesLength : Integer;
-begin
-  // Clone the original header
-  Result := Copy(ABytes);
-
-  // If digest not big enough to contain a nonce, just return the clone
-  ABytesLength := Length(ABytes);
-  if ABytesLength < 4 then
-    exit;
-
-  // Overwrite the nonce in little-endian
-  Result[ABytesLength - 4] := Byte(AValue);
-  Result[ABytesLength - 3] := (AValue SHR 8) AND 255;
-  Result[ABytesLength - 2] := (AValue SHR 16) AND 255;
-  Result[ABytesLength - 1] := (AValue SHR 24) AND 255;
-end;
-
-function TRandomHash2.GetLastDWordLE(const ABytes: TBytes) : UInt32;
-var LLen : Integer;
-begin
-  LLen := Length(ABytes);
-  if LLen < 4 then
-   raise EArgumentOutOfRangeException.CreateRes(@SBlockHeaderTooSmallForNonce);
-
-  // Last 4 bytes are nonce (LE)
-  Result := ABytes[LLen - 4] OR
-           (ABytes[LLen - 3] SHL 8) OR
-           (ABytes[LLen - 2] SHL 16) OR
-           (ABytes[LLen - 1] SHL 24);
-end;
-
 function TRandomHash2.Compress(const AInputs : TArray<TBytes>; ASeed : UInt32): TBytes;
 var
   i: Int32;
@@ -378,32 +352,39 @@ begin
   end;
 end;
 
-function TRandomHash2.GetCachedHashes : TArray<TCachedHash>;
-begin
-  Result := FCachedHashes.ToArray;
-end;
-
-function TRandomHash2.HasCachedHash : Boolean;
-begin
-  Result := FCachedHashes.Count > 0;
-end;
-
-function TRandomHash2.PopCachedHash : TCachedHash;
+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
-  Result := FCachedHashes.Last;
-  FCachedHashes.Delete(FCachedHashes.Count - 1);
-end;
+  LGen := LDisposables.AddObject( TMersenne32.Create (ASeed) ) as TMersenne32;
+  LSize := Length(AInput) + (AExpansionFactor * M);
+  LOutput := Copy(AInput);
+  LBytesToAdd := LSize - Length(AInput);
 
-function TRandomHash2.PeekCachedHash : TCachedHash;
-begin
-  Result := FCachedHashes.Last;
-end;
+  while LBytesToAdd > 0 do
+  begin
+    LNextChunk := Copy(LOutput);
+    if Length(LNextChunk) > LBytesToAdd then
+      SetLength(LNextChunk, LBytesToAdd);
 
-function TRandomHash2.ContencateByteArrays(const AChunk1, AChunk2: TBytes): TBytes;
-begin
-  SetLength(Result, Length(AChunk1) + Length(AChunk2));
-  Move(AChunk1[0], Result[0], Length(AChunk1));
-  Move(AChunk2[0], Result[Length(AChunk1)], Length(AChunk2));
+    LRandom := LGen.NextUInt32;
+    case LRandom mod 8 of
+      0: LOutput := ContencateByteArrays(LOutput, MemTransform1(LNextChunk));
+      1: LOutput := ContencateByteArrays(LOutput, MemTransform2(LNextChunk));
+      2: LOutput := ContencateByteArrays(LOutput, MemTransform3(LNextChunk));
+      3: LOutput := ContencateByteArrays(LOutput, MemTransform4(LNextChunk));
+      4: LOutput := ContencateByteArrays(LOutput, MemTransform5(LNextChunk));
+      5: LOutput := ContencateByteArrays(LOutput, MemTransform6(LNextChunk));
+      6: LOutput := ContencateByteArrays(LOutput, MemTransform7(LNextChunk));
+      7: LOutput := ContencateByteArrays(LOutput, MemTransform8(LNextChunk));
+    end;
+    LBytesToAdd := LBytesToAdd - Length(LNextChunk);
+  end;
+  Result := LOutput;
 end;
 
 function TRandomHash2.MemTransform1(const AChunk: TBytes): TBytes;
@@ -522,39 +503,409 @@ begin
     Result[i] := TBits.RotateRight8(AChunk[i], LChunkLength - i);
 end;
 
-function TRandomHash2.Expand(const AInput: TBytes; AExpansionFactor: Int32; ASeed : UInt32): TBytes;
+function TRandomHash2.ContencateByteArrays(const AChunk1, AChunk2: TBytes): TBytes;
+begin
+  SetLength(Result, Length(AChunk1) + Length(AChunk2));
+  Move(AChunk1[0], Result[0], Length(AChunk1));
+  Move(AChunk2[0], Result[Length(AChunk1)], Length(AChunk2));
+end;
+
+{ TRandomHash2Fast }
+
+constructor TRandomHash2Fast.Create;
+begin
+  FEnableCaching := False;
+  FCachedHashes := TList<TCachedHash>.Create;
+  SetLength(Self.FCachedHeaderTemplate, 0);
+  FHashAlg[0] := THashFactory.TCrypto.CreateSHA2_256();
+  FHashAlg[1] := THashFactory.TCrypto.CreateSHA2_384();
+  FHashAlg[2] := THashFactory.TCrypto.CreateSHA2_512();
+  FHashAlg[3] := THashFactory.TCrypto.CreateSHA3_256();
+  FHashAlg[4] := THashFactory.TCrypto.CreateSHA3_384();
+  FHashAlg[5] := THashFactory.TCrypto.CreateSHA3_512();
+  FHashAlg[6] := THashFactory.TCrypto.CreateRIPEMD160();
+  FHashAlg[7] := THashFactory.TCrypto.CreateRIPEMD256();
+  FHashAlg[8] := THashFactory.TCrypto.CreateRIPEMD320();
+  FHashAlg[9] := THashFactory.TCrypto.CreateBlake2B_512();
+  FHashAlg[10] := THashFactory.TCrypto.CreateBlake2S_256();
+  FHashAlg[11] := THashFactory.TCrypto.CreateTiger2_5_192();
+  FHashAlg[12] := THashFactory.TCrypto.CreateSnefru_8_256();
+  FHashAlg[13] := THashFactory.TCrypto.CreateGrindahl512();
+  FHashAlg[14] := THashFactory.TCrypto.CreateHaval_5_256();
+  FHashAlg[15] := THashFactory.TCrypto.CreateMD5();
+  FHashAlg[16] := THashFactory.TCrypto.CreateRadioGatun32();
+  FHashAlg[17] := THashFactory.TCrypto.CreateWhirlPool();
+  FMemStats.Reset;
+end;
+
+destructor TRandomHash2Fast.Destroy;
+var i : integer;
+begin
+ FCachedHashes.Clear;
+ FreeAndNil(FCachedHashes);
+ for i := Low(FHashAlg) to High(FHashAlg) do
+   FHashAlg[i] := nil;
+ inherited Destroy;
+end;
+
+class function TRandomHash2Fast.Compute(const ABlockHeader: TBytes): TBytes;
 var
-  LSize, LBytesToAdd: Int32;
-  LOutput, LNextChunk: TBytes;
-  LRandom: UInt32;
+  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, 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.ComputeVeneerRound(const ARoundOutputs : TArray<TBytes>) : TBytes;
+var
+  LSeed : UInt32;
+  LSize : UInt32;
+  i : integer;
+  LCachedItem : TCachedHash;
+begin
+  LSeed := GetLastDWordLE(ARoundOutputs[High(ARoundOutputs)]);
+  // Final "veneer" round of RandomHash is a SHA2-256 of compression of prior round outputs
+  Result := FHashAlg[0].ComputeBytes(Compress(ARoundOutputs, LSeed)).GetBytes;
+  // Tally memstats
+  LSize := 0;
+  for i := Low(ARoundOutputs) to High(ARoundOutputs) do
+    Inc(LSize, Length(ARoundOutputs[i]));
+  for i := 0 to FCachedHashes.Count - 1 do begin
+    LCachedItem := FCachedHashes.Items[i];
+    Inc(LSize, Length(LCachedItem.Hash) + Length(LCachedItem.Header) + 4);
+  end;
+  FMemStats.AddDatum(LSize);
+end;
+
+function TRandomHash2Fast.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, LXX : TBytes;
+  LCachedHash : TCachedHash;
+  LParentOutputs, LNeighborOutputs, LToArray, LBuffs2: TArray<TBytes>;
+  LHashFunc: IHash;
+  i: Int32;
   LDisposables : TDisposables;
+  LBuff : TBytes;
 begin
-  LGen := LDisposables.AddObject( TMersenne32.Create (ASeed) ) as TMersenne32;
-  LSize := Length(AInput) + (AExpansionFactor * M);
-  LOutput := Copy(AInput);
-  LBytesToAdd := LSize - Length(AInput);
+  if (ARound < 1) or (ARound > MAX_N) then
+    raise EArgumentOutOfRangeException.CreateRes(@SInvalidRound);
 
-  while LBytesToAdd > 0 do
+  LRoundOutputs := LDisposables.AddObject( TList<TBytes>.Create() ) as TList<TBytes>;
+  LGen := LDisposables.AddObject( TMersenne32.Create(0) ) as TMersenne32;
+  if ARound = 1 then begin
+    LRoundInput := FHashAlg[0].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);
+
+      // If neighbour was a fully evaluated nonce, cache it for re-use
+      if FEnableCaching AND LNeighbourWasLastRound then begin
+        LCachedHash.Nonce := GetLastDWordLE(LNeighbourNonceHeader);
+        LCachedHash.Header := LNeighbourNonceHeader;
+        LCachedHash.Hash := ComputeVeneerRound(LNeighborOutputs);
+        // if header is different (other than nonce), clear cache
+        if NOT BytesEqual(FCachedHeaderTemplate, LCachedHash.Header, 0, 32 - 4) then begin
+          FCachedHashes.Clear;
+          FCachedHeaderTemplate := SetLastDWordLE(LCachedHash.Header, 0);
+        end;
+
+        FCachedHashes.Add(LCachedHash);
+      end;
+    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
-    LNextChunk := Copy(LOutput);
-    if Length(LNextChunk) > LBytesToAdd then
-      SetLength(LNextChunk, LBytesToAdd);
+    LSource := AInputs[LGen.NextUInt32 mod Length(AInputs)];
+    Result[i] := LSource[LGen.NextUInt32 mod Length(LSource)];
+  end;
+end;
 
-    LRandom := LGen.NextUInt32;
-    case LRandom mod 8 of
-      0: LOutput := ContencateByteArrays(LOutput, MemTransform1(LNextChunk));
-      1: LOutput := ContencateByteArrays(LOutput, MemTransform2(LNextChunk));
-      2: LOutput := ContencateByteArrays(LOutput, MemTransform3(LNextChunk));
-      3: LOutput := ContencateByteArrays(LOutput, MemTransform4(LNextChunk));
-      4: LOutput := ContencateByteArrays(LOutput, MemTransform5(LNextChunk));
-      5: LOutput := ContencateByteArrays(LOutput, MemTransform6(LNextChunk));
-      6: LOutput := ContencateByteArrays(LOutput, MemTransform7(LNextChunk));
-      7: LOutput := ContencateByteArrays(LOutput, MemTransform8(LNextChunk));
+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;
-    LBytesToAdd := LBytesToAdd - Length(LNextChunk);
+    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;
+
+function TRandomHash2Fast.GetCachedHashes : TArray<TCachedHash>;
+begin
+  Result := FCachedHashes.ToArray;
+end;
+
+function TRandomHash2Fast.HasCachedHash : Boolean;
+begin
+  Result := FCachedHashes.Count > 0;
+end;
+
+function TRandomHash2Fast.PopCachedHash : TCachedHash;
+begin
+  Result := FCachedHashes.Last;
+  FCachedHashes.Delete(FCachedHashes.Count - 1);
+end;
+
+function TRandomHash2Fast.PeekCachedHash : TCachedHash;
+begin
+  Result := FCachedHashes.Last;
+end;
+
+function TRandomHash2Fast.ContencateByteArrays(const AChunk1, AChunk2: TBytes): TBytes;
+begin
+  SetLength(Result, Length(AChunk1) + Length(AChunk2));
+  Move(AChunk1[0], Result[0], Length(AChunk1));
+  Move(AChunk2[0], Result[Length(AChunk1)], Length(AChunk2));
+end;
+
 end.

+ 25 - 26
src/gui-classic/UFRMDiagnosticTool.lfm

@@ -1,64 +1,63 @@
 object FRMDiagnosticTool: TFRMDiagnosticTool
   Left = 0
-  Height = 648
+  Height = 324
   Top = 0
-  Width = 1140
+  Width = 570
   Caption = 'FRMDiagnosticTool'
-  ClientHeight = 648
-  ClientWidth = 1140
+  ClientHeight = 324
+  ClientWidth = 570
   Color = clBtnFace
-  DesignTimePPI = 192
   Font.Color = clWindowText
-  Font.Height = -23
+  Font.Height = -12
   Font.Name = 'Tahoma'
   OnCreate = FormCreate
   LCLVersion = '2.1.0.0'
   object btnRH: TButton
-    Left = 16
-    Height = 51
-    Top = 16
-    Width = 260
+    Left = 8
+    Height = 26
+    Top = 8
+    Width = 130
     Caption = 'Start Random Hash'
     Font.Color = clWindowText
-    Font.Height = -23
+    Font.Height = -12
     Font.Name = 'Tahoma'
     OnClick = btnRHClick
     ParentFont = False
     TabOrder = 0
   end
   object btnRH2: TButton
-    Left = 288
-    Height = 51
-    Top = 16
-    Width = 260
+    Left = 144
+    Height = 26
+    Top = 8
+    Width = 130
     Caption = 'Start Random Hash 2'
     Font.Color = clWindowText
-    Font.Height = -23
+    Font.Height = -12
     Font.Name = 'Tahoma'
     OnClick = btnRH2Click
     ParentFont = False
     TabOrder = 1
   end
   object txtLog: TMemo
-    Left = 16
-    Height = 153
-    Top = 77
-    Width = 503
+    Left = 8
+    Height = 401
+    Top = 38
+    Width = 822
     Anchors = [akTop, akLeft, akRight, akBottom]
     Font.Color = clWindowText
-    Font.Height = -23
+    Font.Height = -12
     Font.Name = 'Tahoma'
     ParentFont = False
     TabOrder = 2
   end
   object btnRHC: TButton
-    Left = 560
-    Height = 51
-    Top = 16
-    Width = 372
+    Left = 280
+    Height = 26
+    Top = 8
+    Width = 186
     Caption = 'Start Random Hash 2 (Cached)'
     Font.Color = clWindowText
-    Font.Height = -23
+    Font.Height = -12
     Font.Name = 'Tahoma'
     OnClick = btnRHCClick
     ParentFont = False

+ 15 - 9
src/gui-classic/UFRMDiagnosticTool.pas

@@ -104,7 +104,7 @@ type
 
   TRandomHash2Thread = class(TAlgorithmThread)
     private
-      FHasher : TRandomHash2;
+      FHasher : TRandomHash2Fast;
     protected
       function TryNextRound(const AInput : TBytes; out AOutput : TBytes) : Boolean; override;
       function GetMemStats : TStatistics; override;
@@ -116,7 +116,7 @@ type
 
   TRandomHash2CachedThread = class(TAlgorithmThread)
     private
-      FHasher : TRandomHash2;
+      FHasher : TRandomHash2Fast;
     protected
       function TryNextRound(const AInput : TBytes; out AOutput : TBytes) : Boolean; override;
       function GetMemStats : TStatistics; override;
@@ -128,7 +128,7 @@ type
 
   TRandomHash2NonceScan = class(TAlgorithmThread)
     private
-      FHasher : TRandomHash2;
+      FHasher : TRandomHash2Fast;
       FLevel : Integer;
     protected
       function TryNextRound(const AInput : TBytes; out AOutput : TBytes) : Boolean; override;
@@ -162,9 +162,11 @@ end;
 procedure TAlgorithmThread.BCExecute;
 var
  LTC : TTickCount;
+ LInput : TBytes;
  LStartTime, LNotifyStartTime  : TDateTime;
  LTotalHashes, LNotifyHashes : UInt32;
 begin
+  SetLength(LInput, 200);
   while True do begin
     LTotalHashes := 0;
     LNotifyHashes := 0;
@@ -172,9 +174,10 @@ begin
     LNotifyStartTime := LStartTime;
     LTC := TPlatform.GetTickCount;
     while Not FExitLoop do begin
-     if TryNextRound (RandomizeNonce(FLastHash), FLastHash) then begin
+     if TryNextRound (RandomizeNonce(LInput), FLastHash) then begin
        inc(LTotalHashes);
        inc(LNotifyHashes);
+       Move(FLastHash, LInput[High(LInput)-32], 32);  // randomize input
      end;
      if TPlatform.GetElapsedMilliseconds(LTC)>2500 then begin
        FNotifyHashes := LNotifyHashes;
@@ -269,7 +272,8 @@ end;
 constructor TRandomHash2Thread.Create;
 begin
   Inherited Create('Random Hash 2');
-  FHasher := TRandomHash2.Create;
+  FHasher := TRandomHash2Fast.Create;
+  FHasher.EnableCaching := False;
   FDisposables.AddObject(FHasher);
 end;
 
@@ -288,8 +292,9 @@ end;
 
 constructor TRandomHash2CachedThread.Create;
 begin
-  Inherited Create('Random Hash (Cached)');
-  FHasher := TRandomHash2.Create;
+  Inherited Create('Random Hash 2 (Cached)');
+  FHasher := TRandomHash2Fast.Create;
+  FHasher.EnableCaching := True;
   FDisposables.AddObject(FHasher);
 end;
 
@@ -311,8 +316,9 @@ end;
 
 constructor TRandomHash2NonceScan.Create;
 begin
-  Inherited Create('Random Hash (Nonce Scan)');
-  FHasher := TRandomHash2.Create;
+  Inherited Create('Random Hash 2 (Nonce Scan)');
+  FHasher := TRandomHash2Fast.Create;
+  FHasher.EnableCaching := True;
   FDisposables.AddObject(FHasher);
 end;
 

+ 51 - 0
src/libraries/sphere10/UCommon.pas

@@ -52,6 +52,9 @@ function BinStrComp(const Str1, Str2 : String): Integer; // Binary-safe StrComp
 function BytesCompare(const ABytes1, ABytes2: TBytes): integer;
 function BytesEqual(const ABytes1, ABytes2 : TBytes) : boolean; overload; inline;
 function BytesEqual(const ABytes1, ABytes2 : TBytes; AFrom, ALength : UInt32) : boolean; overload; inline;
+function SetLastDWordLE(const ABytes: TBytes; AValue: UInt32): TBytes;
+function GetLastDWordLE(const ABytes: TBytes) : UInt32;
+function GetDWordLE(const ABytes: TBytes; AOffset : Integer) : UInt32;
 function IIF(const ACondition: Boolean; const ATrueResult, AFalseResult: Cardinal): Cardinal; overload;
 function IIF(const ACondition: Boolean; const ATrueResult, AFalseResult: Integer): Integer; overload;
 function IIF(const ACondition: Boolean; const ATrueResult, AFalseResult: Int64): Int64; overload;
@@ -641,6 +644,54 @@ begin
   Result := CompareMem(@ABytes1[AFrom], @ABytes2[AFrom], ALength);
 end;
 
+function SetLastDWordLE(const ABytes: TBytes; AValue: UInt32): TBytes;
+var
+  ABytesLength : Integer;
+begin
+  // Clone the original header
+  Result := Copy(ABytes);
+
+  // If digest not big enough to contain a nonce, just return the clone
+  ABytesLength := Length(ABytes);
+  if ABytesLength < 4 then
+    exit;
+
+  // Overwrite the nonce in little-endian
+  Result[ABytesLength - 4] := Byte(AValue);
+  Result[ABytesLength - 3] := (AValue SHR 8) AND 255;
+  Result[ABytesLength - 2] := (AValue SHR 16) AND 255;
+  Result[ABytesLength - 1] := (AValue SHR 24) AND 255;
+end;
+
+function GetLastDWordLE(const ABytes: TBytes) : UInt32;
+var LLen : Integer;
+begin
+  LLen := Length(ABytes);
+  if LLen < 4 then
+   raise EArgumentException.Create('ABytes needs to be at least 4 bytes');
+
+  // Last 4 bytes are nonce (LE)
+  Result := ABytes[LLen - 4] OR
+           (ABytes[LLen - 3] SHL 8) OR
+           (ABytes[LLen - 2] SHL 16) OR
+           (ABytes[LLen - 1] SHL 24);
+end;
+
+function GetDWordLE(const ABytes: TBytes; AOffset : Integer) : UInt32;
+var LLen : Integer;
+begin
+  LLen := Length(ABytes);
+  if LLen < AOffset+3 then
+   raise EArgumentException.Create('ABytes[AOffset] needs at least 4 more bytes');
+
+  // Last 4 bytes are nonce (LE)
+  Result := ABytes[AOffset + 0] OR
+           (ABytes[AOffset + 1] SHL 8) OR
+           (ABytes[AOffset + 2] SHL 16) OR
+           (ABytes[AOffset + 3] SHL 24);
+end;
+
+
 function IIF(const ACondition: Boolean; const ATrueResult, AFalseResult: Cardinal): Cardinal;
 begin
   if ACondition then

+ 153 - 7
src/tests/URandomHash2.Tests.Delphi.pas

@@ -15,12 +15,35 @@ type
     procedure TestRandomHash2_Standard;
   end;
 
+  { TRandomHash2FastTest }
+
+  TRandomHash2FastTest = class(TPascalCoinUnitTest)
+  published
+    procedure TestRandomHash2_Standard;
+    procedure ReferenceConsistency_RandomHash;
+    procedure CacheConsistency;
+    procedure ReferenceConsistency_Expand;
+    procedure ReferenceConsistency_Compress;
+    procedure ReferenceConsistency_MemTransform1;
+  end;
+
 implementation
 
-uses variants, UCommon, UMemory, URandomHash2, HlpHashFactory, HlpBitConverter, strutils;
+uses variants, UCommon, UMemory, URandomHash, URandomHash2, HlpHashFactory, HlpBitConverter, strutils;
 
 const
 
+  { General purpose byte array for testing }
+
+  DATA_BYTES : String =
+  '0x4f550200ca022000bb718b4b00d6f74478c332f5fb310507e55a9ef9b38551f63858e3f7c86db'+
+  'd00200006f69afae8a6b0735b6acfcc58b7865fc8418897c530211f19140c9f95f2453210270000'+
+  '0000000003000300a297fd17506f6c796d696e65722e506f6c796d696e65722e506f6c796d69393'+
+  '03030303030302184d63666eb166619e925cef2a306549bbc4d6f4da3bdf28b4393d5c1856f0ee3'+
+  'b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855000000006d68295b0'+
+  '0000000';
+
+
   { RandomHash Official Values }
 
   DATA_RANDOMHASH2_STANDARD_INPUT : array[1..3] of String = (
@@ -30,9 +53,9 @@ const
   );
 
   DATA_RANDOMHASH2_STANDARD_EXPECTED : array[1..3] of String = (
-    '0x78b7b1a58fd073a47b02279080ff5b0d1fb673f0477c18a652801ba8fd0cbac8',
-    '0xf6ad93cb45b8749a1d64cd74cf446975cf782990587f9ddd08dfeee2087e7487',
-    '0xa37a261ff74ccb03bbb64bd1c3da5928f580052adcf7ff48596f4297110f20c2'
+    '0x1340164c73924ecdcff3866bd1a5c75c5c9f517c480336fa8189b2a4c2c745c3',
+    '0xe2ba36ce31ed3b01a5f254dfafb392f29b8eafef24c52fd0480c4db5b2d2e999',
+    '0x88624077976ac8327b38092fff01428125d914b98d76b5c9270177ec105a6976'
   );
 
 { TRandomHash2Test }
@@ -43,14 +66,137 @@ var
   LStr, LStr2 : String;
   LB, LB2 : TBytes;
 begin
-  for i := Low(DATA_RANDOMHASH2_STANDARD_INPUT) to High(DATA_RANDOMHASH2_STANDARD_INPUT) do
+  for i := Low(DATA_RANDOMHASH2_STANDARD_INPUT) to High(DATA_RANDOMHASH2_STANDARD_INPUT) do begin
     AssertEquals(ParseBytes(DATA_RANDOMHASH2_STANDARD_EXPECTED[i]), TRandomHash2.Compute(ParseBytes(DATA_RANDOMHASH2_STANDARD_INPUT[i])));
    //WriteLn(Format('%s', [Bytes2Hex(TRandomHash.Compute(ParseBytes(LCase.Input)), True)]));
+  end;
 end;
 
+{ TRandomHash2FastTest }
 
-initialization
+procedure TRandomHash2FastTest.TestRandomHash2_Standard;
+var
+  i : integer;
+  LStr, LStr2 : String;
+begin
+  for i := Low(DATA_RANDOMHASH2_STANDARD_INPUT) to High(DATA_RANDOMHASH2_STANDARD_INPUT) do
+    AssertEquals(ParseBytes(DATA_RANDOMHASH2_STANDARD_EXPECTED[i]), TRandomHash2Fast.Compute(ParseBytes(DATA_RANDOMHASH2_STANDARD_INPUT[i])));
+   //WriteLn(Format('%s', [Bytes2Hex(TRandomHash.Compute(ParseBytes(LCase.Input)), True)]));
+end;
 
-  RegisterTest(TRandomHash2Test.Suite);
+procedure TRandomHash2FastTest.ReferenceConsistency_RandomHash;
+var
+  LInput, LBuff1, LBuff2 : TBytes;
+  LHasher1 : TRandomHash2;
+  LHasher2 : TRandomHash2Fast;
+  LDisposables : TDisposables;
+  i : Integer;
+begin
+  LInput := ParseBytes(DATA_BYTES);
+  LHasher1 := LDisposables.AddObject( TRandomHash2.Create ) as TRandomHash2;
+  LHasher2 := LDisposables.AddObject( TRandomHash2Fast.Create ) as TRandomHash2Fast;
+
+  for i := 1 to 100 do begin
+    LInput := TRandomHash.Compute( LInput );
+    LBuff1 := LHasher1.Hash(LInput);
+    LBuff2 := LHasher2.Hash(LInput);
+    AssertEquals(LBuff1, LBuff2);
+  end;
+end;
+
+procedure TRandomHash2FastTest.CacheConsistency;
+var
+  LBuff : TBytes;
+  LHasher : TRandomHash2Fast;
+  LCachedHash : TRandomHash2Fast.TCachedHash;
+  i, j : Integer;
+  LDisposables : TDisposables;
+begin
+  LBuff := ParseBytes(DATA_BYTES);
+  LHasher := LDisposables.AddObject( TRandomHash2Fast.Create ) as TRandomHash2Fast;
+
+  for i := 1 to 100 do begin
+    LBuff := LHasher.Hash(LBuff);
+    while LHasher.HasCachedHash do begin
+      LCachedHash := LHasher.PopCachedHash;
+      AssertEquals(TRandomHash2Fast.Compute(LCachedHash.Header), LCachedHash.Hash);
+    end;
+  end;
+end;
+
+procedure TRandomHash2FastTest.ReferenceConsistency_Expand;
+var
+  LInput, LBuff1, LBuff2 : TBytes;
+  LHasher1 : TRandomHash2;
+  LHasher2 : TRandomHash2Fast;
+  LDisposables : TDisposables;
+  i : Integer;
+begin
+  LInput := ParseBytes(DATA_BYTES);
+  LHasher1 := LDisposables.AddObject( TRandomHash2.Create ) as TRandomHash2;
+  LHasher2 := LDisposables.AddObject( TRandomHash2Fast.Create ) as TRandomHash2Fast;
+
+  for i := 1 to 100 do begin
+    //LInput := TRandomHash.Compute( LInput );
+    LInput := TBytes.Create(0,1,2,3);
+    LBuff1 := LHasher1.Expand(LInput, 1, i);
+    LBuff2 := LHasher2.Expand(LInput, 1, i);
+    AssertEquals(LBuff1, LBuff2);
+  end;
+end;
 
+procedure TRandomHash2FastTest.ReferenceConsistency_Compress;
+var
+  LInput : TArray<TBytes>;
+  LBuff1, LBuff2 : TBytes;
+  LHasher1 : TRandomHash2;
+  LHasher2 : TRandomHash2Fast;
+  LDisposables : TDisposables;
+begin
+  LInput := TArray<TBytes>.Create( ParseBytes(DATA_BYTES), ParseBytes(DATA_BYTES), ParseBytes(DATA_BYTES)) ;
+  LHasher1 := LDisposables.AddObject( TRandomHash2.Create ) as TRandomHash2;
+  LHasher2 := LDisposables.AddObject( TRandomHash2Fast.Create ) as TRandomHash2Fast;
+  LBuff1 := LHasher1.Compress(LInput, 1234);
+  LBuff2 := LHasher2.Compress(LInput, 1234);
+  AssertEquals(LBuff1, LBuff2);
+end;
+
+procedure TRandomHash2FastTest.ReferenceConsistency_MemTransform1;
+var
+  LInput, LBuff1, LBuff2, LTmp : TBytes;
+  LHasher1 : TRandomHash2;
+  LHasher2 : TRandomHash2Fast;
+  LDisposables : TDisposables;
+  LStartPad, LEndPad, LTransformAmount : Integer;
+begin
+  LInput := ParseBytes(DATA_BYTES);
+  LHasher1 := LDisposables.AddObject( TRandomHash2.Create ) as TRandomHash2;
+  LHasher2 := LDisposables.AddObject( TRandomHash2Fast.Create ) as TRandomHash2Fast;
+
+
+  for LStartPad := 0 to 5 do
+    for LEndPad := 0 to 5 do begin
+      for LTransformAmount := 4 to 10 do begin
+        // Ref
+        LInput := TRandomHash.Compute( LInput );
+        SetLength(LInput, LTransformAmount);
+        LBuff1 := LHasher1.MemTransform1(LInput);
+        SetLength(LBuff1, LTransformAmount);
+
+        // Opt
+        SetLength(LTmp, Length(LInput)+ LStartPad + LTransformAmount + LEndPad);
+        Move(LInput[0], LTmp[LStartPad], Length(LInput));
+        LHasher2.MemTransform1(LTmp, LStartPad, LStartPad + Length(LInput), LTransformAmount);
+        SetLength(LBuff2, LTransformAmount);
+        Move(LTmp[Length(LInput)+LStartPad], LBuff2[0], LTransformAmount);
+
+        // Check
+        AssertEquals(LBuff1, LBuff2);
+      end;
+    end;
+end;
+
+initialization
+  RegisterTest(TRandomHash2Test.Suite);
+  RegisterTest(TRandomHash2FastTest.Suite);
 end.

+ 56 - 38
src/tests/URandomHash2.Tests.pas

@@ -20,10 +20,17 @@ type
   published
     procedure TestRandomHash2_Standard;
     procedure TestRandomHash2;
-    procedure CacheConsistency;
     procedure GetSetLastDWordConsistency;
   end;
 
+  { TRandomHash2FastTest }
+
+  TRandomHash2FastTest = class(TPascalCoinUnitTest)
+  published
+    procedure CacheConsistency;
+    procedure TestRandomHash2_Standard;
+  end;
+
 
   { TRandomHash2StressTest }
 
@@ -47,31 +54,31 @@ const
   { RandomHash Official Values }
 
   DATA_RANDOMHASH_STANDARD : array[1..3] of TTestItem<String, String> = (
-    (Input: '0x0';                                         Expected: '0x78b7b1a58fd073a47b02279080ff5b0d1fb673f0477c18a652801ba8fd0cbac8'),
-    (Input: 'The quick brown fox jumps over the lazy dog'; Expected: '0xf6ad93cb45b8749a1d64cd74cf446975cf782990587f9ddd08dfeee2087e7487'),
-    (Input: '0x000102030405060708090a0b0c0d0e0f';          Expected: '0xa37a261ff74ccb03bbb64bd1c3da5928f580052adcf7ff48596f4297110f20c2')
+    (Input: '0x0';                                         Expected: '0x1340164c73924ecdcff3866bd1a5c75c5c9f517c480336fa8189b2a4c2c745c3'),
+    (Input: 'The quick brown fox jumps over the lazy dog'; Expected: '0xe2ba36ce31ed3b01a5f254dfafb392f29b8eafef24c52fd0480c4db5b2d2e999'),
+    (Input: '0x000102030405060708090a0b0c0d0e0f';          Expected: '0x88624077976ac8327b38092fff01428125d914b98d76b5c9270177ec105a6976')
   );
 
   {  Hash Test Data }
 
   DATA_RANDOMHASH : array[1..16] of TTestItem<Integer, String> = (
     { NOTE: Input denotes the number of bytes to take from DATA_BYTES when executing test }
-    (Input: 17;  Expected: '0x28e348e6a865e5333e4528e0743b4965248cac41904e11d9011a50cb19513fb6'),
-    (Input: 31;  Expected: '0x656a42062355319bd643a9eff87cc04d14ea442384e7dd0932113911f2823024'),
-    (Input: 32;  Expected: '0xd666b74b91c2ddfb54d663bafb369c53055a875ababbdf1f510db39dd73d86fa'),
-    (Input: 33;  Expected: '0x163b3758fb5dfb896f469f9914df1f43d966a8ff3d1e710ed51fdcfc7e425308'),
-    (Input: 34;  Expected: '0x97a3618f57f9477cd4ba91397cd7856ecd05f8d8206b077828cd52e8ecdcd4c5'),
-    (Input: 63;  Expected: '0x93f998f385413219f4ca9b764e3f69f90100fdf43d4434d0f8fe4bcf4ed98ae6'),
-    (Input: 64;  Expected: '0x709a93634fe1927f4b94570049ebf33ac4c1bd750392df070e44d59261d763e9'),
-    (Input: 65;  Expected: '0x2ef12aab3156b3e25551ed7c1dfbf51c6dfc84510cb6cc46b405bae02578f1b0'),
-    (Input: 100; Expected: '0x6cf7f41cb8ec80ae512e68322f67bf381821599d39a2007cb6d1857b3458947a'),
-    (Input: 117; Expected: '0xa3ce45f763b30dc74181b4ad4a02631318f93cdf3cc4f4054b01561df29c53fd'),
-    (Input: 127; Expected: '0x1c042113abb5c41265916be7092acf2000a148668b0bca9671e9cdbdf13dc99a'),
-    (Input: 128; Expected: '0xda552e27ca43bcfc8c636a4c58041027f9f0a06b0d40031c889fe3a673b89701'),
-    (Input: 129; Expected: '0x4b1b1d161ff80435ea4a6839530258d33d3105b5812def2b1b65bf543df06e62'),
-    (Input: 178; Expected: '0x66cbb61b4b927ca60c4ca04d999b0d8398b9fa9cf1db2866956b246f11cbc692'),
-    (Input: 199; Expected: '0x3705f63d6242acd77fbc262010b34101165c7cc6c0c9bbd607bbc118d1626a5f'),
-    (Input: 200; Expected: '0xcde9da66925e5b363aaca02dac5c473e81ab6a0f1b7a6b6dbac9d0ebec383e22')
+    (Input: 17;  Expected: '0x4cc3fb94697855e5d124823331516e5a18623d87d735e67ca3f2f7b87fcdabee'),
+    (Input: 31;  Expected: '0xb4655c784cc21d0da5a19420c66ed275c8881952b9119d0b215882880069813c'),
+    (Input: 32;  Expected: '0x2e2de78bb4db374779108541ac1eeb36a1f1144b2767e7e1aa8dac19c5430958'),
+    (Input: 33;  Expected: '0xb096070aec5b4f6146cc5b70c85ab4e0b94efb5aa3ca41e7fd671fd3b98c8588'),
+    (Input: 34;  Expected: '0x015b89e6c6b2b4580702555adda7b445e1064f481442475061a4768e65879eb0'),
+    (Input: 63;  Expected: '0x868b30a0091d6562fa3dffe9c5ec5df2214383f2c6f48bbf2b314f8791e089e9'),
+    (Input: 64;  Expected: '0xfe90fe080b0dca79aec5e0b269e58d368d9a83ce73171d155f475c42642034d0'),
+    (Input: 65;  Expected: '0xec04a9b78b4f4ecf1e48d5c11b89f87f461ea5ae23eaff9d2baa0c703e14d01b'),
+    (Input: 100; Expected: '0x42e42defa9c03dd9ff74053a6bffbc966321aa14df446bb36a270844fdb8e838'),
+    (Input: 117; Expected: '0xaf4257f5d406ab3e3d0453129fefdf6633016676bb3aa45a40fdac2f5bca4951'),
+    (Input: 127; Expected: '0xa998117cd3f3505b4ed863601abc175a393653ce3eec3b375235ad530845045f'),
+    (Input: 128; Expected: '0xb4a76d37c5f390986ace3e35b7306c65a62d92f3f449a5f7b523982a5e5496a4'),
+    (Input: 129; Expected: '0x432d52101c90b268a7127d84c080f8d5c60d0223620e5c463883b15ed81db390'),
+    (Input: 178; Expected: '0x4ab88684c0ef33bc8dad4a4aa1a9badd025bc7afe98657d325e3bf1e4f3706a2'),
+    (Input: 199; Expected: '0x25aa461b64f96f7b62b46e79273554a7b5866f26278929ae3f78203a714cca2a'),
+    (Input: 200; Expected: '0x3bd8f64cc0eeb0c8dedba03a1f142763c3e6f9aed80dcd6534eac28eba2f7510')
   );
 
 { TRandomHash2Test }
@@ -97,39 +104,51 @@ begin
   end;
 end;
 
-procedure TRandomHash2Test.CacheConsistency;
+procedure TRandomHash2Test.GetSetLastDWordConsistency;
 var
-  LBuff : TBytes;
+  LBuff, LBuff2 : TBytes;
   LHasher : TRandomHash2;
-  LCachedHash : TRandomHash2.TCachedHash;
-  i, j : Integer;
+  LCachedHash : TRandomHash2Fast.TCachedHash;
+  i  : UInt32;
   LDisposables : TDisposables;
 begin
   LBuff := ParseBytes(DATA_BYTES);
   LHasher := LDisposables.AddObject( TRandomHash2.Create ) as TRandomHash2;
-
   for i := 1 to 100 do begin
     LBuff := LHasher.Hash(LBuff);
-    while LHasher.HasCachedHash do begin
-      LCachedHash := LHasher.PopCachedHash;
-      AssertEquals(TRandomHash2.Compute(LCachedHash.Header), LCachedHash.Hash);
-    end;
+    AssertEquals(32768 + i, GetLastDWordLE(SetLastDWordLE(LBuff, 32768 + i)));
   end;
 end;
 
-procedure TRandomHash2Test.GetSetLastDWordConsistency;
+{ TRandomHash2FastTest }
+
+procedure TRandomHash2FastTest.TestRandomHash2_Standard;
 var
-  LBuff, LBuff2 : TBytes;
-  LHasher : TRandomHash2;
-  LCachedHash : TRandomHash2.TCachedHash;
-  i  : UInt32;
+  LCase : TTestItem<String, String>;
+begin
+  for LCase in DATA_RANDOMHASH_STANDARD do
+    AssertEquals(ParseBytes(LCase.Expected), TRandomHash2Fast.Compute(ParseBytes(LCase.Input)));
+    //WriteLn(Format('%s', [Bytes2Hex(TRandomHash2.Compute(ParseBytes(LCase.Input)), True)]));
+end;
+
+
+procedure TRandomHash2FastTest.CacheConsistency;
+var
+  LBuff : TBytes;
+  LHasher : TRandomHash2Fast;
+  LCachedHash : TRandomHash2Fast.TCachedHash;
+  i, j : Integer;
   LDisposables : TDisposables;
 begin
   LBuff := ParseBytes(DATA_BYTES);
-  LHasher := LDisposables.AddObject( TRandomHash2.Create ) as TRandomHash2;
+  LHasher := LDisposables.AddObject( TRandomHash2Fast.Create ) as TRandomHash2Fast;
+
   for i := 1 to 100 do begin
     LBuff := LHasher.Hash(LBuff);
-    AssertEquals(32768 + i, LHasher.GetLastDWordLE(LHasher.SetLastDWordLE(LBuff, 32768 + i)));
+    while LHasher.HasCachedHash do begin
+      LCachedHash := LHasher.PopCachedHash;
+      AssertEquals(TRandomHash2Fast.Compute(LCachedHash.Header), LCachedHash.Hash);
+    end;
   end;
 end;
 
@@ -151,12 +170,11 @@ begin
   // no exceptions should occur
 end;
 
-
-
 initialization
 
 {$IFDEF FPC}
   RegisterTest(TRandomHash2Test);
+  RegisterTest(TRandomHash2FastTest);
   RegisterTest(TRandomHash2StressTest);
 {$ELSE}
   //TDUnitX.RegisterTextFixture(TRandomHashTest);