Bläddra i källkod

Core: added RandomHash reference implementation (BETA)

Herman Schoenfeld 7 år sedan
förälder
incheckning
c994fa4136

+ 521 - 0
src/core/URandomHash.pas

@@ -0,0 +1,521 @@
+unit URandomHash;
+
+{
+  Copyright (c) Herman Schoenfeld
+
+  RandomHash reference implementation (PIP-0009).
+
+  PROVIDED FOR CODE-REVIEW PURPOSES ONLY. ACTUAL HASH OUTPUTS WILL BE DIFFERENT WITH PENDING COMMITS.
+
+  Distributed under the MIT software license, see the accompanying file LICENSE
+  or visit http://www.opensource.org/licenses/mit-license.php.
+}
+
+
+{%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 Generics.Collections, SysUtils, HlpIHash, HlpBits, HlpHashFactory;
+
+type
+
+  { TRandomHash }
+
+  TRandomHash = class sealed(TObject)
+    const
+      N = 5;            // Number of hashing rounds required to compute a nonce (total rounds = 2^N - 1)
+      M = 10 * 1024;    // 10KB The memory expansion unit (in bytes)
+
+    private
+      FMurmurHash3_x86_32 : 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;
+      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) : TBytes;
+      function Compress(const AInputs: TArray<TBytes>): TBytes; inline;
+      function ChangeNonce(const ABlockHeader: TBytes; ANonce: Int32): TBytes; inline;
+      function Checksum(const AInput: TBytes): UInt32; overload; inline;
+      function Checksum(const AInput: TArray<TBytes>): UInt32; overload; inline;
+      function Hash(const ABlockHeader: TBytes; ARound: Int32) : TArray<TBytes>; overload;
+    public
+      constructor Create;
+      destructor Destroy;
+      function Hash(const ABlockHeader: TBytes): TBytes; overload; inline;
+      class function Compute(const ABlockHeader: TBytes): TBytes; overload; static; inline;
+  end;
+
+  { ERandomHash }
+
+  ERandomHash = class(Exception);
+
+  { TMersenne32 }
+
+  TMersenne32 = class
+    private const
+      // Define MT19937 constants (32-bit RNG)
+      N = 624;
+      M = 397;
+      R = 31;
+      A = $9908B0DF;
+      F = 1812433253;
+      U = 11;
+      S = 7;
+      B = $9D2C5680;
+      T = 15;
+      C = $EFC60000;
+      L = 18;
+      MASK_LOWER = UInt32((UInt64(1) shl R) - 1);
+      MASK_UPPER = UInt32(UInt64(1) shl R);
+    private
+      FIndex: Word;
+      Fmt: array [0..N-1] of UInt32;
+      procedure Twist(); inline;
+    public
+      constructor Create(ASeed: UInt32);
+      procedure Initialize(ASeed: UInt32); inline;
+      function NextInt32: UInt32; inline;
+      function NextUInt32: UInt32; inline;
+      function NextSingle: Single; inline;
+      function NextUSingle: Single; inline;
+  end;
+
+
+resourcestring
+  SUnSupportedHash = 'Unsupported Hash Selected';
+  SInvalidRound = 'Round must be between 0 and N inclusive';
+
+implementation
+
+uses UMemory;
+
+{ TRandomHash }
+
+constructor TRandomHash.Create;
+begin
+  FMurmurHash3_x86_32 := THashFactory.THash32.CreateMurmurHash3_x86_32();
+  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();
+end;
+
+destructor TRandomHash.Destroy;
+var i : integer;
+begin
+ FMurmurHash3_x86_32 := nil;
+ for i := Low(FHashAlg) to High(FHashAlg) do
+   FHashAlg[i] := nil;
+end;
+
+class function TRandomHash.Compute(const ABlockHeader: TBytes): TBytes;
+var
+  LHasher : TRandomHash;
+  LDisposables : TDisposables;
+begin
+ LHasher := LDisposables.AddObject( TRandomHash.Create ) as TRandomHash;
+ Result := LHasher.Hash(ABlockHeader);
+end;
+
+function TRandomHash.Hash(const ABlockHeader: TBytes): TBytes;
+var
+  LAllOutputs: TArray<TBytes>;
+  LSeed: UInt32;
+begin
+  LAllOutputs := Hash(ABlockHeader, N);
+  Result := FHashAlg[0].ComputeBytes(Compress(LAllOutputs)).GetBytes;
+end;
+
+function TRandomHash.Hash(const ABlockHeader: TBytes; ARound: Int32) : TArray<TBytes>;
+var
+  LRoundOutputs: TList<TBytes>;
+  LSeed: UInt32;
+  LGen: TMersenne32;
+  LRoundInput, LOtherNonceHeader, LOutput, LBytes: TBytes;
+  LParentOutputs, LNeighborOutputs, LToArray: TArray<TBytes>;
+  LHashFunc: IHash;
+  i: Int32;
+  LDisposables : TDisposables;
+begin
+  if (ARound < 1) or (ARound > 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
+    LSeed := Checksum(ABlockHeader);
+    LGen.Initialize(LSeed);
+    LRoundInput := ABlockHeader;
+  end else begin
+    LParentOutputs := Hash(ABlockHeader, ARound - 1);
+    LSeed := Checksum(LParentOutputs);
+    LGen.Initialize(LSeed);
+    LRoundOutputs.AddRange( LParentOutputs );
+    LOtherNonceHeader := ChangeNonce(ABlockHeader, LGen.NextUInt32);
+    LNeighborOutputs := Hash(LOtherNonceHeader, ARound - 1);
+    LRoundOutputs.AddRange(LNeighborOutputs);
+    LRoundInput := Compress( LRoundOutputs.ToArray );
+  end;
+
+  LHashFunc := FHashAlg[LGen.NextUInt32 mod 18];
+  LOutput := LHashFunc.ComputeBytes(LRoundInput).GetBytes;
+  LOutput := Expand(LOutput, N - ARound);
+  LRoundOutputs.Add(LOutput);
+
+  Result := LRoundOutputs.ToArray;
+end;
+
+
+function TRandomHash.ChangeNonce(const ABlockHeader: TBytes;  ANonce: Int32): TBytes;
+const
+  LNonceOffset = 4;
+begin
+ // IMPORTANT: Prior to RandomHash, when calculating the proof-of-work the hashable digest
+ // includes the nonce at a variable offset. Post RandomHash, the hashable digest
+ // includes the nonce at fixed offset 4.
+
+ // Clone the original header
+ Result := System.Copy(ABlockHeader);
+
+ // If digest not big enough to contain a nonce, just return the clone
+ if Length(ABlockHeader) < (LNonceOffset + 4) then
+   exit;
+
+ // Overwrite the nonce in little-endian
+ Result[LNonceOffset+0] := Byte(ANonce);
+ Result[LNonceOffset+1] := (ANonce SHR 8) AND 255;
+ Result[LNonceOffset+2] := (ANonce SHR 16) AND 255;
+ Result[LNonceOffset+3] := (ANonce SHR 24) AND 255;
+end;
+
+function TRandomHash.Checksum(const AInput: TBytes): UInt32;
+begin
+  Result := FMurmurHash3_x86_32.ComputeBytes(AInput).GetUInt32;
+end;
+
+function TRandomHash.Checksum(const AInput : TArray<TBytes>): UInt32;
+var
+  i: Int32;
+begin
+  FMurmurHash3_x86_32.Initialize;
+  for i := System.Low(AInput) to System.High(AInput) do
+  begin
+    FMurmurHash3_x86_32.TransformBytes(AInput[i]);
+  end;
+  Result := FMurmurHash3_x86_32.TransformFinal.GetUInt32;
+end;
+
+function TRandomHash.Compress(const AInputs : TArray<TBytes>): TBytes;
+var
+  i: Int32;
+  LSeed: UInt32;
+  LSource: TBytes;
+  LGen: TMersenne32;
+  LDisposables : TDisposables;
+begin
+  System.SetLength(Result, 100);
+  LSeed := Checksum(AInputs);
+  LGen := LDisposables.AddObject( TMersenne32.Create( LSeed ) ) as TMersenne32;
+  for i := 0 to 99 do
+  begin
+    LSource := AInputs[LGen.NextUInt32 mod System.Length(AInputs)];
+    Result[i] := LSource[LGen.NextUInt32 mod System.Length(LSource)];
+  end;
+end;
+
+function TRandomHash.ContencateByteArrays(const AChunk1, AChunk2: TBytes): TBytes;
+begin
+  System.SetLength(Result, System.Length(AChunk1) + System.Length(AChunk2));
+  System.Move(AChunk1[0], Result[0], System.Length(AChunk1));
+  System.Move(AChunk2[0], Result[System.Length(AChunk1)], System.Length(AChunk2));
+end;
+
+function TRandomHash.MemTransform1(const AChunk: TBytes): TBytes;
+begin
+  Result := AChunk;
+end;
+
+function TRandomHash.MemTransform2(const AChunk: TBytes): TBytes;
+var
+  i, LChunkLength, LPivot: Int32;
+begin
+  LChunkLength := System.Length(AChunk);
+  LPivot := LChunkLength SHR 1;
+  System.SetLength(Result, LChunkLength);
+  System.Move(AChunk[LPivot], Result[0], LPivot);
+  System.Move(AChunk[System.Low(AChunk)], Result[LPivot], LPivot);
+end;
+
+function TRandomHash.MemTransform3(const AChunk: TBytes): TBytes;
+var
+  i, LChunkLength: Int32;
+begin
+  LChunkLength := System.Length(AChunk);
+  System.SetLength(Result, LChunkLength);
+  for i := System.Low(AChunk) to System.High(AChunk) do
+    Result[i] := AChunk[LChunkLength - i - 1];
+end;
+
+function TRandomHash.MemTransform4(const AChunk: TBytes): TBytes;
+var
+  i, LChunkLength, LPivot: Int32;
+begin
+  LChunkLength := System.Length(AChunk);
+  LPivot := LChunkLength shr 1;
+  System.SetLength(Result, LChunkLength);
+  for i := System.Low(AChunk) to System.Pred(LPivot) do
+  begin
+    Result[(i * 2)] := AChunk[i];
+    Result[(i * 2) + 1] := AChunk[i + LPivot];
+  end;
+end;
+
+function TRandomHash.MemTransform5(const AChunk: TBytes): TBytes;
+var
+  i, LChunkLength, LPivot: Int32;
+begin
+  LChunkLength := System.Length(AChunk);
+  LPivot := LChunkLength shr 1;
+  System.SetLength(Result, LChunkLength);
+  for i := System.Low(AChunk) to System.Pred(LPivot) do
+  begin
+    Result[(i * 2)] := AChunk[i + LPivot];
+    Result[(i * 2) + 1] := AChunk[i];
+  end;
+end;
+
+function TRandomHash.MemTransform6(const AChunk: TBytes): TBytes;
+var
+  i, LChunkLength, LPivot: Int32;
+begin
+  LChunkLength := System.Length(AChunk);
+  LPivot := LChunkLength shr 1;
+  System.SetLength(Result, LChunkLength);
+  for i := System.Low(AChunk) to System.Pred(LPivot) do
+  begin
+    Result[i] := AChunk[(i * 2)] xor AChunk[(i * 2) + 1];
+    Result[i + LPivot] := AChunk[i + 1] xor AChunk[LChunkLength - i - 1];
+  end;
+end;
+
+function TRandomHash.MemTransform7(const AChunk: TBytes): TBytes;
+var
+  i, LChunkLength: Int32;
+begin
+  LChunkLength := System.Length(AChunk);
+  System.SetLength(Result, LChunkLength);
+  for i := System.Low(AChunk) to System.High(AChunk) do
+    Result[i] := Byte(TBits.RotateLeft32(AChunk[i], i));
+end;
+
+function TRandomHash.MemTransform8(const AChunk: TBytes): TBytes;
+var
+  i, LChunkLength: Int32;
+begin
+  LChunkLength := System.Length(AChunk);
+  System.SetLength(Result, LChunkLength);
+  for i := System.Low(AChunk) to System.High(AChunk) do
+    Result[i] := Byte(TBits.RotateRight32(AChunk[i], i));
+end;
+
+function TRandomHash.Expand(const AInput: TBytes; AExpansionFactor: Int32): TBytes;
+var
+  LSize, LBytesToAdd: Int32;
+  LOutput, LNextChunk: TBytes;
+  LRandom, LSeed: UInt32;
+  LGen: TMersenne32;
+  LDisposables : TDisposables;
+begin
+  LSeed := Checksum(AInput);
+  LGen := LDisposables.AddObject( TMersenne32.Create (LSeed) ) as TMersenne32;
+  LSize := System.Length(AInput) + (AExpansionFactor * M);
+  LOutput := System.Copy(AInput);
+  LBytesToAdd := LSize - System.Length(AInput);
+
+  while LBytesToAdd > 0 do
+  begin
+    LNextChunk := System.Copy(LOutput);
+    if System.Length(LNextChunk) > LBytesToAdd then
+      System.SetLength(LNextChunk, LBytesToAdd);
+
+    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 - System.Length(LNextChunk);
+  end;
+  Result := LOutput;
+end;
+
+{ TMersenne32 }
+
+constructor TMersenne32.Create(ASeed: UInt32);
+begin
+  Initialize(ASeed);
+end;
+
+procedure TMersenne32.Initialize(ASeed: UInt32);
+var
+  i: Int32;
+begin
+  Fmt[0] := ASeed;
+  for i := 1 to System.Pred(N) do
+    Fmt[i] := F * (Fmt[i - 1] xor (Fmt[i - 1] shr 30)) + i;
+  FIndex := N;
+end;
+
+procedure TMersenne32.Twist;
+var
+  i: Int32;
+begin
+  for i := 0 to N - M - 1 do
+    Fmt[i] := Fmt[i + M] xor (((Fmt[i] and MASK_UPPER) or (Fmt[i + 1] and MASK_LOWER)) shr 1) xor (UInt32(-(Fmt[i + 1] and 1)) and A);
+
+  for i := N - M to N - 2 do
+  begin
+    Fmt[i] := Fmt[i + (M - N)] xor (((Fmt[i] and MASK_UPPER) or (Fmt[i + 1] and MASK_LOWER)) shr 1) xor (UInt32(-(Fmt[i + 1] and 1)) and A);
+    Fmt[N - 1] := Fmt[M - 1] xor (((Fmt[N - 1] and MASK_UPPER) or (Fmt[0] and MASK_LOWER)) shr 1) xor (UInt32(-(Fmt[0] and 1)) and A);
+  end;
+  FIndex := 0;
+end;
+
+function TMersenne32.NextInt32: UInt32;
+begin
+  Result := Int32(NextUInt32);
+end;
+
+function TMersenne32.NextUInt32: UInt32;
+var
+  i: Int32;
+begin
+  i := FIndex;
+  if FIndex >= N then
+  begin
+    Twist;
+    i := FIndex;
+  end;
+  Result := Fmt[i];
+  FIndex := i + 1;
+  Result := Result xor (Fmt[i] shr U);
+  Result := Result xor (Result shl S) and B;
+  Result := Result xor (Result shl T) and C;
+  Result := Result xor (Result shr L);
+end;
+
+function TMersenne32.NextSingle: Single;
+begin
+  Result := NextUInt32 * 4.6566128730773926E-010;
+end;
+
+function TMersenne32.NextUSingle: Single;
+begin
+  Result := NextUInt32 * 2.32830643653869628906E-10;
+end;
+
+end.

+ 7 - 2
src/pascalcoin_wallet.lpi

@@ -36,7 +36,7 @@
         <PackageName Value="LCL"/>
       </Item2>
     </RequiredPackages>
-    <Units Count="95">
+    <Units Count="96">
       <Unit0>
         <Filename Value="pascalcoin_wallet.dpr"/>
         <IsPartOfProject Value="True"/>
@@ -545,8 +545,13 @@
         <Filename Value="gui\UCTRLNoAccount.pas"/>
         <IsPartOfProject Value="True"/>
         <ComponentName Value="CTRLNoAccounts"/>
+        <HasResources Value="True"/>
         <ResourceBaseClass Value="Form"/>
       </Unit94>
+      <Unit95>
+        <Filename Value="core\URandomHash.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit95>
     </Units>
   </ProjectOptions>
   <CompilerOptions>
@@ -557,7 +562,7 @@
     </Target>
     <SearchPaths>
       <IncludeFiles Value="$(ProjOutDir);libraries\generics.collections\inc"/>
-      <OtherUnitFiles Value="core;gui;libraries\synapse;libraries\sphere10;libraries\generics.collections;libraries\pascalcoin;core.utils;gui\wizards\wallet;gui\wizards\operations;gui-classic;gui\wizards\accounts"/>
+      <OtherUnitFiles Value="core;gui;libraries\synapse;libraries\sphere10;libraries\generics.collections;libraries\pascalcoin;core.utils;gui\wizards\wallet;gui\wizards\operations;gui-classic"/>
       <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/>
     </SearchPaths>
     <Parsing>

+ 32 - 0
src/tests/RandomHash.Tests.dpr

@@ -0,0 +1,32 @@
+program RandomHash.Tests;
+{
+
+  Delphi DUnit Test Project
+  -------------------------
+  This project contains the DUnit test framework and the GUI/Console test runners.
+  Add "CONSOLE_TESTRUNNER" to the conditional defines entry in the project options
+  to use the console test runner.  Otherwise the GUI test runner will be used by
+  default.
+
+}
+
+{$WARN DUPLICATE_CTOR_DTOR OFF}
+{$IFDEF CONSOLE_TESTRUNNER}
+{$APPTYPE CONSOLE}
+{$ENDIF}
+
+uses
+  Forms,
+  TestFramework,
+  GUITestRunner,
+  TextTestRunner,
+  URandomHash in '..\core\URandomHash.pas',
+  RandomHashTests in 'RandomHashTests.pas';
+
+begin
+Application.Initialize;
+  if IsConsole then
+    TextTestRunner.RunRegisteredTests
+  else
+    GUITestRunner.RunRegisteredTests;
+end.

+ 68 - 0
src/tests/RandomHashTests.pas

@@ -0,0 +1,68 @@
+unit RandomHashTests;
+
+interface
+
+uses
+  Classes, SysUtils, {$IFDEF FPC}fpcunit,testregistry,{$ELSE}TestFramework,{$ENDIF FPC}
+  URandomHash;
+
+type
+  { TTestRandomHash }
+
+  TTestRandomHash = class(TTestCase)
+    protected
+      procedure SetUp; override;
+      procedure TearDown; override;
+    published
+      procedure TestEmpty;
+      procedure TestQuickBrownFox;
+  end;
+
+implementation
+
+uses UCommon;
+
+{ TTestRandomHash }
+
+procedure TTestRandomHash.SetUp;
+begin
+  inherited;
+end;
+
+procedure TTestRandomHash.TearDown;
+begin
+  inherited;
+end;
+
+procedure TTestRandomHash.TestEmpty;
+var
+  LDigest, LHash : TBytes;
+  LHashHex : AnsiString;
+begin
+  LDigest := TEncoding.ASCII.GetBytes('');
+  LHash := TRandomHash.Compute(LDigest);
+  LHashHex := Buffer2Hex(LHash);
+  // TODO: need to tweak RandomHash with Polyminer to establish correctness
+end;
+
+procedure TTestRandomHash.TestQuickBrownFox;
+var
+  LDigest, LHash : TBytes;
+  LHashHex : AnsiString;
+begin
+  LDigest := TEncoding.ASCII.GetBytes('The quick brown fox jumps over the lazy dog');
+  LHash := TRandomHash.Compute(LDigest);
+  LHashHex := Buffer2Hex(LHash);
+  // TODO: need to tweak RandomHash with Polyminer to establish correctnessend;
+end;
+
+initialization
+
+
+{$IFDEF FPC}
+  RegisterTest(TTestRandomHash);
+{$ELSE}
+  RegisterTest(TTestRandomHash.Suite);
+{$ENDIF FPC}
+
+end.

+ 120 - 0
src/tests/UPascalCoinUnitTests.lpi

@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="10"/>
+    <PathDelim Value="\"/>
+    <General>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="PascalCoinUnitTests"/>
+      <ResourceType Value="res"/>
+      <UseXPManifest Value="True"/>
+    </General>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="3">
+      <Item1>
+        <PackageName Value="fpcunittestrunner"/>
+      </Item1>
+      <Item2>
+        <PackageName Value="LCL"/>
+      </Item2>
+      <Item3>
+        <PackageName Value="FCL"/>
+      </Item3>
+    </RequiredPackages>
+    <Units Count="11">
+      <Unit0>
+        <Filename Value="UPascalCoinUnitTests.lpr"/>
+        <IsPartOfProject Value="True"/>
+      </Unit0>
+      <Unit1>
+        <Filename Value="..\core\URandomHash.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit1>
+      <Unit2>
+        <Filename Value="RandomHashTests.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit2>
+      <Unit3>
+        <Filename Value="..\libraries\generics.collections\generics.collections.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit3>
+      <Unit4>
+        <Filename Value="..\libraries\generics.collections\generics.defaults.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit4>
+      <Unit5>
+        <Filename Value="..\libraries\generics.collections\generics.hashes.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit5>
+      <Unit6>
+        <Filename Value="..\libraries\generics.collections\generics.helpers.pas"/>
+        <IsPartOfProject Value="True"/>
+        <UnitName Value="Generics.Helpers"/>
+      </Unit6>
+      <Unit7>
+        <Filename Value="..\libraries\generics.collections\generics.memoryexpanders.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit7>
+      <Unit8>
+        <Filename Value="..\libraries\generics.collections\generics.strings.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit8>
+      <Unit9>
+        <Filename Value="..\libraries\sphere10\UMemory.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit9>
+      <Unit10>
+        <Filename Value="..\libraries\sphere10\UCommon.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit10>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <PathDelim Value="\"/>
+    <Target>
+      <Filename Value=".\bin\PascalCoinUnitTests"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <OtherUnitFiles Value="..\core;..\libraries\generics.collections;..\libraries\hashlib4pascal;..\libraries\sphere10"/>
+      <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <CodeGeneration>
+      <Optimizations>
+        <OptimizationLevel Value="4"/>
+      </Optimizations>
+    </CodeGeneration>
+    <Linking>
+      <Options>
+        <Win32>
+          <GraphicApplication Value="True"/>
+        </Win32>
+      </Options>
+    </Linking>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 18 - 0
src/tests/UPascalCoinUnitTests.lpr

@@ -0,0 +1,18 @@
+program UPascalCoinUnitTests;
+
+{$mode objfpc}{$H+}
+
+uses
+  Interfaces,
+  Forms,
+  GuiTestRunner,
+  RandomHashTests;
+
+{$R *.res}
+
+begin
+  Application.Initialize;
+  Application.CreateForm(TGuiTestRunner, TestRunner);
+  Application.Run;
+end.
+

BIN
src/tests/UPascalCoinUnitTests.res