|
@@ -23,15 +23,15 @@ In short, the purpose of RandomHash2 is to address the the need for faster block
|
|
|
|
|
|
Whilst RandomHash appears to have empirically achieved it's GPU and ASIC resistivity goals, it's computationally-heavy nature has resulted in an unforeseen consequence of slow blockchain validation.
|
|
|
|
|
|
-First of all, RandomHash introduces the concept of nonce-dependency between nonces. This means to evaluate a nonce, the partial evaluation of other random neighboring nonces is required. This allows RandomHash to operate in two modes, validation mode and mining mode.
|
|
|
+First of all, RandomHash introduces the concept of nonce-dependencies between themselves. This means in order to evaluate the hash for a block-header with some nonce, the partial evaluation of the same header with random "neighbouring nonces" is required. This allows RandomHash to operate in two modes, validation mode and mining mode.
|
|
|
|
|
|
-In validation mode, RandomHash is simply used to hash a block-header in order to verify it's the correct block in the blockchain -- just the same as how SHA2-256D does in Bitcoin.
|
|
|
+In validation mode, RandomHash is simply used to hash a block-header in order to verify it's position in a blockchain -- just the same as how SHA2-256D does in Bitcoin.
|
|
|
|
|
|
-In mining mode, RandomHash is used to mine the next block by enumerating many nonces to to find an acceptable Proof-of-Work for the next block. In this mode, RandomHash exploits the partial calculations from previous rounds by resuming them in subsequent hashing rounds. In RandomHash, this mode operates at twice the hashrate as validation mode, and is the basis for the "CPU Bias" and important to achieve GPU resistivity. It is biased towards CPU's since re-using the partially calculated nonces is a serial optimization that cannot be efficiently exploited in a parallelized, batch-computation context (such as GPU mining). In other words, the optimal nonce-set is enumerated on-the-fly and cannot be pre-determined into a range for parallel mining as GPU mining requires.
|
|
|
+In mining mode, RandomHash is used to mine the next block-header by enumerating many nonces to find an acceptable Proof-of-Work solution for the next block. In this mode, RandomHash exploits the partial calculations from previous rounds by resuming them in subsequent hashing rounds. In RandomHash 1, this mode operates at twice the hashrate as validation mode (200%), and is the basis for the "CPU Bias" and important to achieve GPU resistivity. It is biased towards CPU's since re-using the partially calculated nonces is a serial optimization that cannot be efficiently exploited in a parallelized, batch-computation context (such as GPU mining). In other words, the optimal nonce-set is enumerated on-the-fly and cannot be pre-determined into a range for parallel mining as GPU mining requires.
|
|
|
|
|
|
However, on a typical 2019 desktop computer, the validation hashrate of RandomHash is approximately 20 hashes per second. At 288 blocks per day, that's 14 seconds to validate a days worth of blocks and over 1 hour to validate 1 year of blocks. Whilst multi-threading, performance tuning and other optimizations have significantly optimized this performance oversight, it remains a fundamental a long-term issue that needs to be resolved.
|
|
|
|
|
|
-RandomHash2 offers an order of magnitude improvement in both validation and mining hashrate. In RandomHash2, the same machine validates at ~300 hashes per second yet mines at ~1,000 hashes per second with far less memory-hardness. Whilst not empirically tested against GPU performance, these numbers suggest a 333% CPU bias.
|
|
|
+RandomHash2 offers an order of magnitude improvement in both validation and mining hashrate. In RandomHash2, the same machine validates at ~400 hashes per second yet mines at ~2,000 hashes per second with far less memory-hardness. Whilst not empirically tested against GPU performance, these numbers suggest a 500% CPU bias.
|
|
|
|
|
|
#### RandomHash vs RandomHash2 Measured Performance
|
|
|
|
|
@@ -39,8 +39,8 @@ RandomHash2 offers an order of magnitude improvement in both validation and mini
|
|
|
| :------------------------- | :------------------------ | :-------------------- | :------------------- | :------------------- | :------------------ |
|
|
|
| RandomHash (validation) | 23 | 5,018,876 | 5,018,628 | 5,019,116 | 83.86 |
|
|
|
| RandomHash (mining) | 44 | 7,528,288 | 7,527,872 | 7,528,732 | 136 |
|
|
|
-| RandomHash2 (validation) | 309 | 16,719 | 1,380 | 221,536 | 29,420 |
|
|
|
-| **RandomHash2 (mining)** | 1,051 | 16,693 | 1,312 | 251,104 | 29,374 |
|
|
|
+| RandomHash2 (validation) | 401 | 16,361 | 560 | 73,872 | 14,934 |
|
|
|
+| **RandomHash2 (mining)** | 2,109 | 74,642 | 1,048 | 157,944 | 15,460 |
|
|
|
|
|
|
_**Machine**: AMD FX-8150 8 Core 3.60 Ghz utilizing 1 thread_
|
|
|
|
|
@@ -58,7 +58,7 @@ RandomHash2 is similarly structured to RandomHash, but has some key differences.
|
|
|
2. ```N``` varies per nonce in a non-deterministic manner between ```MIN_N=2``` and ```MAX_N=4```, inclusive;
|
|
|
3. Each level in (1) also depends on ```J``` neighboring nonces, determined randomly and non-deterministically;
|
|
|
4. The value ```J``` is restricted to ```MIN_J=0``` and ```MAX_J=4```;
|
|
|
-5. Each level selects a random hash function from a set of 18 well-known hash algorithms;
|
|
|
+5. Each level selects a random hash function from a set of 77 well-known cryptographic hash algorithms;
|
|
|
6. The input digest hashed at a level is the compression of the transitive closure of the hash outputs of all it's prior-levels (1) and it's neighbouring nonce's prior-levels (3);
|
|
|
7. The output of every level is expanded for (low) memory-hardness;
|
|
|
8. Randomness is generated using ```Mersenne Twister``` algorithm;
|
|
@@ -74,7 +74,7 @@ The primary differences to predecessor are:
|
|
|
```pascal
|
|
|
MIN_N = 2;
|
|
|
MAX_N = 4;
|
|
|
- MIN_J = 0;
|
|
|
+ MIN_J = 1;
|
|
|
MAX_J = 8;
|
|
|
M = 256;
|
|
|
```
|
|
@@ -128,13 +128,105 @@ if LNeighbourWasLastRound then begin
|
|
|
end;
|
|
|
```
|
|
|
|
|
|
+**There are now 77 internally used hash algorithms, not 18**
|
|
|
+
|
|
|
+In order to strengthen ASIC and GPU resistance, RandomHash2 now employs 77 internal hash algorithms to transform the data. This moves the economic costs of an ASIC far beyond viability for any rational economic actor, and since the sequence in which these 77 hash algorithms are used is in-determinable and random on a nonce-by-nonce basis. This number is also such that it makes GPU-programs (kernels) similarly non-viable, although not to the same degree as an ASIC.
|
|
|
+
|
|
|
+The below are the algorithms in their indices as they appear within RandomHash2 algorithm.
|
|
|
+
|
|
|
+Blake2B_160, Blake2B_256, Blake2B_512, Blake2B_384, Blake2S_128, Blake2S_160, Blake2S_224, Blake2S_256, Gost, GOST3411_2012_256, GOST3411_2012_512, Grindahl256, Grindahl512, HAS160, Haval_3_128, Haval_3_160, Haval_3_192, Haval_3_224, Haval_3_256, Haval_4_128, Haval_4_160, Haval_4_192, Haval_4_224, Haval_4_256, Haval_5_128, Haval_5_160, Haval_5_192, Haval_5_224, Haval_5_256, Keccak_224, Keccak_256, Keccak_288, Keccak_384, Keccak_512, MD2, MD5, MD4, Panama, RadioGatun32, RIPEMD, RIPEMD128, RIPEMD160, RIPEMD256, RIPEMD320, SHA0, SHA1, SHA2_224, SHA2_256, SHA2_384, SHA2_512, SHA2_512_224, SHA2_512_256, SHA3_224, SHA3_256, SHA3_384, SHA3_512, Snefru_8_128, Snefru_8_256, Tiger_3_128, Tiger_3_160, Tiger_3_192, Tiger_4_128, Tiger_4_160, Tiger_4_192, Tiger_5_128, Tiger_5_160, Tiger_5_192, Tiger2_3_128, Tiger2_3_160, Tiger2_3_192, Tiger2_4_128, Tiger2_4_160, Tiger2_4_192, Tiger2_5_128, Tiger2_5_160, Tiger2_5_192, WhirlPool
|
|
|
+
|
|
|
+```pascal
|
|
|
+
|
|
|
+ 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();
|
|
|
+
|
|
|
+
|
|
|
+```
|
|
|
+
|
|
|
+
|
|
|
### Analysis
|
|
|
|
|
|
#### Cryptographic Strength
|
|
|
|
|
|
Since hashing starts and ends with a ```SHA2-256``` the cryptographic strength of RandomHash2 is **at least** that of ```SHA2-256D``` as used in Bitcoin. Even if one were to assume the data transformations in between the start/end were cryptographically insecure, it wouldn't change this minimum security guarantee.
|
|
|
|
|
|
-However, the transformations in between are not weak and involve the use of 18 other cryptographically strong hash algorithms. As a result, RandomHash2 is orders of magnitude more stronger than standard cryptographic hash algorithms since they are combined in random, non-determinstic ways. However this achievement is paid for by significant performance overhead (which is intentional).
|
|
|
+However, the transformations in between are not weak and involve the use of 77 other cryptographically strong hash algorithms. As a result, RandomHash2 is orders of magnitude more stronger than standard cryptographic hash algorithms since they are combined in random, non-determinstic ways. However this achievement is paid for by significant performance overhead (which is intentional).
|
|
|
|
|
|
However, within the 18 hash algorithms used, some are considered "cryptographically weak" such as MD5. The use of some weak algorithms is inconsequential to overall security since their purpose is not to add to security but to computational complexity to prevent ASIC manufacturing.
|
|
|
|
|
@@ -171,13 +263,14 @@ In order to determine if this balance is achieved, an empirical nonce-scanning a
|
|
|
|
|
|
| N | Mean Hashrate (H/s) | Mean Mem Per Hash (b) | Min Mem Per Hash (b) | Max Mem Per Hash (b) | Sample Std Dev. (b) |
|
|
|
| :------ | :------------------------ | :-------------------- | :------------------- | :------------------- | :------------------ |
|
|
|
-| 2 (min) | 240 | 4,175 | 1,312 | 7,184 | 1,854 |
|
|
|
-| 3 | 651 | 5,984 | 1,312 | 49,436 | 6,293 |
|
|
|
-| 4 (max) | 1,051 | 16,693 | 1,312 | 251,104 | 29,374 |
|
|
|
+| 2 (min) | 964 | 1,462 | 760 | 2,292 | 443 |
|
|
|
+| 3 | 1,708 | 1,978 | 760 | 14,844 | 6,293 |
|
|
|
+| 4 (max) | 2,109 | 74,642 | 1,048 | 157,944 | 15,460 |
|
|
|
+
|
|
|
|
|
|
_**Machine**: AMD FX-8150 8 Core 3.60 Ghz utilizing 1 thread_
|
|
|
|
|
|
-As the above table shows, this balance is achieved. Nonce-scanning (via CPU) yields no benefit whatsoever and in fact incurs a hashrate penalty. Also, it is the opinion of the author that any future optimization would change this balance since it would benefit all levels proportionally. However, a line of inquiry is to investigate if whether or not the reduced memory-hardness of nonce-scanning may yield a benefit for GPU-based nonce-scanning attack. In any event, the result of this attack is only to gain higher hashrate and **does not compromise the cryptographic security** of the blockchain whatsoever.
|
|
|
+As the above table shows, this balance is achieved. Nonce-scanning (via CPU) yields no observable benefit and in fact incurs a hashrate penalty. Also, it is the opinion of the author that any future optimization would **not** change this balance since an optimization would benefit all nonce scanning levels proportionally. However, a worthy line of inquiry is whether or not the reduced memory-hardness of nonce-scanning at lower levels may yield some optimization benefit for GPU-based miner. In any event, the result of this attack is only to gain higher hashrate and **does not compromise the cryptographic security** of the blockchain whatsoever.
|
|
|
|
|
|
#### CPU Bias
|
|
|
|
|
@@ -217,108 +310,132 @@ A reference implementation of RandomHash can be found [here][2]. A full implemen
|
|
|
|
|
|
```pascal
|
|
|
|
|
|
- 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
|
|
|
- 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)
|
|
|
- NUM_HASH_ALGO = 18;
|
|
|
-
|
|
|
- public type
|
|
|
-
|
|
|
- TCachedHash = record
|
|
|
- Nonce : UInt32;
|
|
|
- Header : TBytes;
|
|
|
- Hash : TBytes;
|
|
|
- end;
|
|
|
-
|
|
|
- private
|
|
|
- FMurmurHash3_x86_32 : IHash;
|
|
|
- FHashAlg : array[0..17] of IHash; // declared here to avoid race-condition during mining
|
|
|
- FCachedHeaderTemplate : TBytes;
|
|
|
- FCachedHashes : TList<TCachedHash>;
|
|
|
-
|
|
|
- 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;
|
|
|
- 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 CalculateRoundOutputs(const ABlockHeader: TBytes; ARound: Int32; out ARoundOutputs : TArray<TBytes>) : Boolean; overload;
|
|
|
- public
|
|
|
- constructor Create;
|
|
|
- destructor Destroy; override;
|
|
|
- property CachedHashes : TArray<TCachedHash> read GetCachedHashes;
|
|
|
- function HasCachedHash : Boolean; inline;
|
|
|
- function PopCachedHash : TCachedHash; inline;
|
|
|
- function PeekCachedHash : TCachedHash; inline;
|
|
|
- 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;
|
|
|
-
|
|
|
- { 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';
|
|
|
+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; // Number of internal cryptographic hash algorithms used
|
|
|
+ SHA2_256_IX = 47; // index of SHA2-256 within the 77 algorithms used
|
|
|
+
|
|
|
+private
|
|
|
+ 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;
|
|
|
|
|
|
-implementation
|
|
|
+{ ERandomHash2 }
|
|
|
|
|
|
-uses UMemory, URandomHash;
|
|
|
+ERandomHash2 = class(Exception);
|
|
|
|
|
|
-{ TRandomHash2 }
|
|
|
+{ TRandomHash2 implementation }
|
|
|
|
|
|
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();
|
|
|
- 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();
|
|
|
+ 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
|
|
|
- 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;
|
|
@@ -353,7 +470,7 @@ var
|
|
|
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;
|
|
|
+ Result := FHashAlg[SHA2_256_IX].ComputeBytes(Compress(ARoundOutputs, LSeed)).GetBytes;
|
|
|
end;
|
|
|
|
|
|
function TRandomHash2.CalculateRoundOutputs(const ABlockHeader: TBytes; ARound: Int32; out ARoundOutputs : TArray<TBytes>) : Boolean;
|
|
@@ -363,7 +480,6 @@ var
|
|
|
LSeed, LNumNeighbours: UInt32;
|
|
|
LGen: TMersenne32;
|
|
|
LRoundInput, LNeighbourNonceHeader, LOutput : TBytes;
|
|
|
- LCachedHash : TCachedHash;
|
|
|
LParentOutputs, LNeighborOutputs, LToArray, LBuffs2: TArray<TBytes>;
|
|
|
LHashFunc: IHash;
|
|
|
i: Int32;
|
|
@@ -376,7 +492,7 @@ begin
|
|
|
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;
|
|
|
+ LRoundInput := FHashAlg[SHA2_256_IX].ComputeBytes(ABlockHeader).GetBytes;
|
|
|
LSeed := GetLastDWordLE( LRoundInput );
|
|
|
LGen.Initialize(LSeed);
|
|
|
end else begin
|
|
@@ -397,19 +513,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 );
|
|
@@ -428,39 +531,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;
|
|
@@ -477,32 +547,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 := 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;
|
|
@@ -620,43 +697,9 @@ begin
|
|
|
for i := 0 to High(AChunk) do
|
|
|
Result[i] := TBits.RotateRight8(AChunk[i], LChunkLength - i);
|
|
|
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 := 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;
|
|
|
|
|
|
-end.
|
|
|
|
|
|
```
|
|
|
|