Rewrite continued:
[core.git] / contrib / chash / chash.php
1 <?php
2 error_reporting(E_ALL | E_STRICT);
3
4 define('START_TIME'            , microtime(true));
5 define('CHECK_POINT'           , 'chash.pos');
6
7 // Hashes needed to complete a "block"
8 $GLOBALS['block_size']          = 100;
9 $GLOBALS['none_increment']      = (1 / pow(10, 20));
10
11 // Hashing algorythm
12 $GLOBALS['hash_algo']           = MHASH_SHA256;
13
14 // Automatic saving interval in seconds
15 $GLOBALS['flush_file_time']     = 30;
16
17 /*
18  * How long (in seconds) to try to find a proper hash until the best root hash
19  * is taken.
20  */
21 $GLOBALS['restart_search_time'] = 1800;
22
23 // Hashes per call
24 $GLOBALS['hash_cycles'] = 5;
25
26 // Total restarts
27 $GLOBALS['total_restarts'] = 0;
28
29 // Found hashes
30 $GLOBALS['found_hashes'] = array(0 => array());
31
32 /**
33  * Continued-hashing
34  *
35  * @author              Roland Haeder <roland@mxchange.org>
36  * @copyright   Copyright (c) 2013 by Core Developer Team
37  * @license             See LICENSE (public-domain)
38  */
39
40 /**
41  * Calculates a simple but stronger hash from given string. No salts are being
42  * added here.
43  *
44  * @param       $str    The string to be hashed
45  * @return      $hash   The hash from string $str
46  */
47 function hashString ($str) {
48         // Calculate strong hash from given string
49         $hash = mhash($GLOBALS['hash_algo'], $str);
50
51         // Return it hexadecimal-encoded
52         return bin2hex($hash);
53 }
54
55 /**
56  * Multiple-hashes given string. This is done by hashing the given string and
57  * then hashing the generated hash again.
58  *
59  * @param       $str    The string to be hashed 4 times
60  * @return      $hash   The generated hash
61  */
62 function multipleHashString ($str) {
63         // Generate hash from given hash
64         $hash = hashString($str);
65
66         // Now over-hash it
67         for ($idx = 0; $idx < ($GLOBALS['hash_cycles'] - 1); $idx++) {
68                 // Over-hash the given hash
69                 $hash = hashString($hash);
70         } // END - for
71
72         // Return it
73         return $hash;
74 }
75
76 /**
77  * Calculates a "modula-hash" based given two hashes.
78  *
79  * @param       $hash1  Hash 1
80  * @param       $hash2  Hash 2
81  */
82 function modulaHash ($hash1, $hash2) {
83         // Both must have same length
84         assert(strlen($hash1) === strlen($hash2));
85
86         // Init propability array with 256 zeros
87         $propability = array_fill(0, 256, 0);
88
89         // Init new hash
90         $modulaHash = '';
91
92         // "Walk" trough first hash and get every 2 byte of both hashes
93         for ($idx = 0; $idx < strlen($hash1); $idx += 2) {
94                 // Init modula value
95                 $mod = 0;
96
97                 // Get both hash parts and convert to ASCII number
98                 $part1 = hexdec(substr($hash1, $idx, 2));
99                 $part2 = hexdec(substr($hash2, $idx, 2));
100
101                 // Debug message
102                 //* NOISY-DEBUG: */ print 'part1=' . $part1 . ',part2=' . $part2 . PHP_EOL;
103
104                 /*
105                  * If part1 is larget part2, part1 is divident and vise-versa. But don't do it
106                  * if one is zero
107                  */
108                 if (($part1 > $part2) && ($part2 > 0)) {
109                         // 'part1' is larger than 'part2'
110                         $mod = $part1 % $part2;
111                 } elseif (($part2 > $part1) && ($part1 > 0)) {
112                         // 'part2' is larger than 'part1'
113                         $mod = $part2 % $part1;
114                 }
115
116                 // $mod is now mostly a small number so try to "improve" it
117                 //* NOISY-DEBUG: */ print 'mod[' . gettype($mod) . ']=' . $mod . ' - BEFORE!' . PHP_EOL;
118                 $mod = (int) round(sqrt($mod * ($part1 + $part2 + $mod ^ 7) / 3));
119                 //* NOISY-DEBUG: */ print 'mod[' . gettype($mod) . ']=' . $mod . ' - AFTER!' . PHP_EOL;
120
121                 // Make sure it is valid
122                 assert($mod >= 0);
123                 assert($mod <= 255);
124
125                 // "Invert" the result against 255 as zeros are not good for later calculations
126                 $mod = 255 - $mod;
127
128                 // Add it to propability array for debugging
129                 $propability[$mod]++;
130
131                 // Encode to hex, pre-pad it with zeros and add to new hash
132                 $modulaHash .= padHex($mod);
133         } // END - for
134
135         // Debug propability array
136         $cnt = 0;
137         foreach ($propability as $value) {
138                 // Is the value larger than one, means the number has been found at least once?
139                 if ($value > 0) {
140                         // Then count it
141                         $cnt++;
142                 } // END - if
143         } // END - foreach
144
145         // Debug message
146         //* NOISY-DEBUG: */ print('cnt=' . $cnt . '/' . strlen($hash1) / 2 . PHP_EOL);
147
148         // Modula hash must have same length as input hash
149         assert(strlen($modulaHash) === strlen($hash1));
150
151         // Return modula hash
152         return $modulaHash;
153 }
154
155 /**
156  * Calculates a "sqrt-hash" based given two hashes and single-hash it
157  *
158  * @param       $hash1  Hash 1
159  * @param       $hash2  Hash 2
160  */
161 function sqrtHash ($hash1, $hash2) {
162         // Both must have same length
163         assert(strlen($hash1) === strlen($hash2));
164
165         // Init new hash
166         $sqrtHash = '';
167
168         // "Walk" trough first hash and get every 2 byte of both hashes
169         for ($idx = 0; $idx < strlen($hash1); $idx += 2) {
170                 // Init modula value
171                 $mod = 0;
172
173                 // Get both hash parts and convert to ASCII number
174                 $part1 = hexdec(substr($hash1, $idx, 2));
175                 $part2 = hexdec(substr($hash2, $idx, 2));
176
177                 // Calculate square root of both parts being multiplied and round up, then "invert" it against 255
178                 $sqrt = intval(255 - ceil(sqrt($part1 * $part2)));
179
180                 // Encode to hex, pre-pad it with zeros and add to new hash
181                 $sqrtHash .= padHex($sqrt);
182         } // END - for
183
184         // "sqrt-hash" must have same length as input hash
185         assert(strlen($sqrtHash) === strlen($hash1));
186
187         // Hash reversed "sqrt-hash" again and return it
188         return hashString(strrev($sqrtHash));
189 }
190
191 /**
192  * Converts a number between 0 and 255 into a zero-padded hexadecimal string
193  *
194  * @param       $num    Number between 0 and 255
195  * @return      $hex    Hexadecimal string, padded with zeros
196  */
197 function padHex ($num) {
198         // Must be a integer number and between 0 and 255
199         assert(is_int($num));
200         assert($num >= 0);
201         assert($num <= 255);
202
203         // Convert it
204         $hex = str_pad(dechex($num), 2, '0', STR_PAD_LEFT);
205
206         // ... and return it
207         return $hex;
208 }
209
210 /**
211  * Calculates sum from given hash
212  *
213  * @param       $hash   Hash to calculate sum from
214  * @return      $sum    Sum from given hash
215  */
216 function calculateSumFromHash ($hash) {
217         // Everything starts with zero ...
218         $sum = 0;
219
220         // Loop through hash
221         for ($idx = 0; $idx < (strlen($hash) / 2); $idx++) {
222                 // And add it
223                 $sum = $sum + hexdec(substr($hash, $idx, 2));
224         } // END - for
225
226         // And return it
227         return $sum;
228 }
229
230 /**
231  * Calculates new nonce
232  *
233  * @return      void
234  */
235 function calculateNonce () {
236         // Linear incrementation
237         $GLOBALS['nonce'] += $GLOBALS['none_increment'];
238 }
239
240 /**
241  * Writes/flushes check-point file
242  *
243  * @param       $hash   Modula hash (or hash to save)
244  * @return      void
245  */
246 function flushCheckPointFile ($hash) {
247         // Display message
248         print ('FLUSHING: Writing ' . count($GLOBALS['found_hashes']) . ' blocks ...' . PHP_EOL);
249
250         // Start timer
251         $timer = microtime(true);
252
253         // Flush data
254         file_put_contents(
255                 CHECK_POINT,
256                 $GLOBALS['total_blocks'] . ':' .
257                 $GLOBALS['total_reward'] . ':' .
258                 $GLOBALS['total_hashes'] . ':' .
259                 $GLOBALS['total_found'] . ':' .
260                 $GLOBALS['total_restarts'] . ':' .
261                 $GLOBALS['hash_cycles'] . ':' .
262                 base64_encode((float) $GLOBALS['nonce']) . ':' .
263                 $hash . ':' .
264                 $GLOBALS['root_hash'] . ':' .
265                 base64_encode(gzcompress(json_encode($GLOBALS['found_hashes'])))
266         );
267
268         // Set time
269         $GLOBALS['time_flush'] = microtime(true);
270         print ('FLUSHING: Took ' . ($GLOBALS['time_flush'] - $timer) . ' seconds.' . PHP_EOL);
271 }
272
273 /**
274  * Adds a found hash and flushes the checkpoint file
275  *
276  * @param       $hash   Hash to save
277  */
278 function addFoundHash ($hash) {
279         // Increment counter
280         $GLOBALS['total_found']++;
281
282         // Add hash to array
283         array_push($GLOBALS['found_hashes'][$GLOBALS['total_blocks']], array(
284                 'modula_hash'  => $GLOBALS['modula_hash'],
285                 'genesis_hash' => $GLOBALS['genesis_hash'],
286                 'root_hash'    => $GLOBALS['root_hash'],
287                 'nonce'        => (float) $GLOBALS['nonce'],
288                 'iter'         => $GLOBALS['iteration'],
289                 'hashes_block' => $GLOBALS['hashes_block'],
290                 'hash_cycles'  => $GLOBALS['hash_cycles'],
291                 'nonce_hash'   => $hash
292         ));
293
294         // Found hash:
295         print ('FOUND: hash=' . $hash . ',nonce=' . $GLOBALS['nonce'] . ',total_found=' . $GLOBALS['total_found'] . PHP_EOL);
296
297         // Set time as a new hash was found
298         $GLOBALS['found_time'] = microtime(true);
299
300         // Flush check-point file after new hash is found
301         flushCheckPointFile($hash);
302
303         // Use nonceHash as next modula hash
304         setModulaHash($hash);
305 }
306
307 /**
308  * Initializes nonce
309  *
310  * @return      void
311  */
312 function initNonce () {
313         $GLOBALS['nonce'] = 1 / (mt_rand() ^ pi());
314         print (__FUNCTION__ . ': nonce=' . $GLOBALS['nonce'] . PHP_EOL);
315 }
316
317 /**
318  * Sets modula hash and calculates sum of it
319  *
320  * @param       $hash   Hash to set as "modula hash"
321  * @return      void
322  */
323 function setModulaHash ($hash) {
324         $GLOBALS['modula_hash'] = $hash;
325         $GLOBALS['sum_modula']  = calculateSumFromHash($GLOBALS['modula_hash']);
326 }
327
328 /*
329  * Calculate "genesis" hashes, please note that these "genesis strings" are now
330  * known to the public as you can read them here in source code and therefore I
331  * will not use them for the real genesis hashes.
332  */
333 $gensisHashes = array(
334         // A famous quote from Deus Ex 2 - Invisible War
335         multiplehashString('"Informations must be free." - AI Helios from Deus Ex'),
336         // My name + URL of my first StatusNet instance
337         multipleHashString('Roland Haeder, https://status.mxchange.org'),
338         // A famous quote from Linus Torwalds
339         multipleHashString('"Software is like sex. Its better when its free." - Linus Torwalds'),
340         // Well ...
341         multipleHashString('September 11 is a big lie.'),
342
343         // GNU is not Uni*
344         multipleHashString('GNU is Not Uni*.'),
345         // WINE is not an emulator
346         multipleHashString('WINE Is Not an Emulator.'),
347         // FlightGear - Fly free!
348         multipleHashString('FlightGear - Fly free!'),
349         // Quote from Linus Torwalds
350         multipleHashString('Your code is shit. Your argument is shit.'),
351 );
352
353 // Calculate "modula hash" from 1st/4th and 2nd/3rd
354 $modulaHashes = array(
355         // "Block" 0
356         modulaHash($gensisHashes[0], $gensisHashes[3]),
357         modulaHash($gensisHashes[1], $gensisHashes[2]),
358
359         // "Block" 1
360         modulaHash($gensisHashes[4], $gensisHashes[7]),
361         modulaHash($gensisHashes[5], $gensisHashes[6]),
362 );
363
364 // Calculate "sqrt hash"
365 $sqrtHashes = array(
366         sqrtHash($modulaHashes[0], $modulaHashes[1]),
367         sqrtHash($modulaHashes[2], $modulaHashes[3])
368 );
369
370 // Calulcate modula hash
371 setModulaHash(multipleHashString(modulaHash($sqrtHashes[0], $sqrtHashes[1])));
372
373 // This is also the "genesis" hash and first root hash
374 $GLOBALS['genesis_hash'] = $GLOBALS['modula_hash'];
375 $GLOBALS['root_hash']    = $GLOBALS['modula_hash'];
376
377 // Output results
378 print ('hashes=' . print_r($gensisHashes, true));
379 print ('modulaHashes=' . print_r($modulaHashes, true));
380 print ('sqrtHashes=' . print_r($sqrtHashes, true));
381 print ('modulaHash=' . $GLOBALS['modula_hash'] . PHP_EOL);
382
383 // Total reward + hashes
384 $GLOBALS['total_reward']   = 0;
385 $GLOBALS['total_hashes']   = 0;
386 $GLOBALS['total_found']    = 0;
387 $GLOBALS['total_blocks']   = 0;
388 $GLOBALS['found_time']     = microtime(true);
389
390 // Is the check point there?
391 if (is_readable(CHECK_POINT)) {
392         // Then load it
393         $checkPoint = file_get_contents(CHECK_POINT);
394
395         // Explode it
396         $data = explode(':', $checkPoint);
397
398         // Assert on count
399         assert(count($data) == 10);
400
401         // 1st element is nonce, 2nd hash, 3rd found hashes
402         $GLOBALS['total_blocks']   = $data[0];
403         $GLOBALS['total_reward']   = $data[1];
404         $GLOBALS['total_hashes']   = $data[2];
405         $GLOBALS['total_found']    = $data[3];
406         $GLOBALS['total_restarts'] = $data[4];
407         $GLOBALS['hash_cycles']    = intval($data[5]);
408         $GLOBALS['nonce']          = (float) base64_decode($data[6]);
409         $GLOBALS['root_hash']      = $data[8];
410         $GLOBALS['found_hashes']   = json_decode(gzuncompress(base64_decode($data[9])));
411
412         // Set modula hash
413         setModulaHash($data[7]);
414 } else {
415         // Create nonce (small)
416         initNonce();
417 }
418
419 // Output again
420 print ('modulaHash=' . $GLOBALS['modula_hash'] . PHP_EOL);
421 print ('nonce=' . $GLOBALS['nonce'] . PHP_EOL);
422 print ('found=' . count($GLOBALS['found_hashes'][$GLOBALS['total_blocks']]) . PHP_EOL);
423
424 // Start "mining"
425 while (true) {
426         // Init hash-per-block counter and hashrate
427         $GLOBALS['hashes_block'] = 0;
428         $hashrate = 0;
429
430         // Wait for block_size iterations (= found hashes). This is one block
431         $timeBlock   = microtime(true);
432         $timeDisplay = $timeBlock;
433         $GLOBALS['time_flush'] = $timeBlock;
434
435         // Time waited for a good block again (no iteration)
436         $timeBadHashes = 0;
437
438         while (count($GLOBALS['found_hashes'][$GLOBALS['total_blocks']]) <= $GLOBALS['block_size']) {
439                 // Create hash from modulaHash ("genesis hash") and nonce
440                 $nonceHash = multipleHashString($GLOBALS['nonce'] . $GLOBALS['modula_hash']);
441
442                 // Calculate sums
443                 $sumNonce  = calculateSumFromHash($nonceHash);
444
445                 // Init counter
446                 $GLOBALS['iteration'] = 0;
447                 $GLOBALS['iteration_second'] = 0;
448
449                 // Now start the "mining" ...
450                 $timeHash = microtime(true);
451                 while ($sumNonce < $GLOBALS['sum_modula']) {
452                         // Calculate new nonce
453                         calculateNonce();
454
455                         // And hash again
456                         $nonceHash = multipleHashString($GLOBALS['nonce'] . $GLOBALS['modula_hash']);
457
458                         // Calculate sums
459                         $sumNonce  = calculateSumFromHash($nonceHash);
460
461                         // Time spend in loop
462                         $testTime = abs(microtime(true) - $timeDisplay);
463
464                         // Calculate hashrate/sec
465                         $hashrate = 1 / $testTime * $GLOBALS['iteration_second'] * $GLOBALS['hash_cycles'];
466
467                         // Only every second
468                         if ($testTime >= 1) {
469                                 // Display hash rate
470                                 print ('hashrate=' . round($hashrate) . ' hashes/sec,iterSecond=' . $GLOBALS['iteration_second'] . ' iterations/sec' . PHP_EOL);
471
472                                 // Reset timer
473                                 $timeDisplay = microtime(true);
474                                 $GLOBALS['iteration_second']  = 0;
475                         } // END - if
476
477                         // Time spend from last flush
478                         $testTime = abs(microtime(true) - $GLOBALS['time_flush']);
479
480                         // Only once per 10 seconds
481                         if ($testTime >= $GLOBALS['flush_file_time']) {
482                                 // Flush check-point file
483                                 flushCheckPointFile($GLOBALS['modula_hash']);
484                         } // END - if
485
486                         // Time spend from last found block
487                         $testTime = abs(microtime(true) - $GLOBALS['found_time']);
488
489                         // Is the last found time to far away?
490                         if ($testTime >= $GLOBALS['restart_search_time']) {
491                                 // Count up restart
492                                 $GLOBALS['total_restarts']++;
493
494                                 // Output message
495                                 print('total_restarts=' . $GLOBALS['total_restarts'] . ' - Restarting ...');
496
497                                 // Count all root (genesis) hashes
498                                 $rootHashes = array();
499                                 foreach ($GLOBALS['found_hashes'] as $block) {
500                                         // "Walk" through all blocks
501                                         foreach ($block as $hash) {
502                                                 if (!isset($hash['root_hash'])) {
503                                                         // Bad file
504                                                         die('INCONSISTENCY: hash=' . print_r($hash, true));
505                                                 } // END - if
506
507                                                 if (isset($rootHashes[$hash['root_hash']])) {
508                                                         // Count up
509                                                         $rootHashes[$hash['root_hash']]++;
510                                                 } else {
511                                                         // First entry found
512                                                         $rootHashes[$hash['root_hash']] = 1;
513                                                 }
514                                         } // END - foreach
515                                 } // END - foreach
516
517                                 // Find best root hash
518                                 $bestRootHash = '';
519                                 $bestRootCount = 0;
520                                 foreach ($rootHashes as $hash => $count) {
521                                         // Debug message
522                                         //* NOISY-DEBUG: */ print ('hash=' . $hash . ',count=' . $count . ',bestRootHash=' . $bestRootHash . ',bestRootCount=' . $bestRootCount . PHP_EOL);
523
524                                         // Is a better one found?
525                                         if ($count > $bestRootCount) {
526                                                 // Remember it
527                                                 $bestRootHash  = $hash;
528                                                 $bestRootCount = $count;
529                                         } // END - if
530                                 } // END - foreach
531
532                                 // Output message
533                                 print ('bestRootHash=' . $bestRootHash . ',bestRootCount=' . $bestRootCount . PHP_EOL);
534
535                                 // Search for latest best root hash
536                                 foreach ($GLOBALS['found_hashes'] as $block) {
537                                         // "Walk" through whole block and search for first appearance of best root hash
538                                         foreach ($block as $idx => $hash) {
539                                                 // Is the root hash there?
540                                                 if ($hash['root_hash'] == $bestRootHash) {
541                                                         // Set found modula hash as new root and current modula hash
542                                                         $GLOBALS['root_hash']   = $hash['nonce_hash'];
543                                                         setModulaHash($hash['nonce_hash']);
544                                                         print ('idx=' . $idx . ',modulaHash=' . $GLOBALS['root_hash'] . ' - Is now new root hash!' . PHP_EOL);
545
546                                                         // Reset "found time" (when a hash was found)
547                                                         $GLOBALS['found_time'] = microtime(true);
548
549                                                         // Re-initialize nonce
550                                                         initNonce();
551
552                                                         // Abort search
553                                                         break;
554                                                 } // END - if
555                                         } // END - for
556                                 } // END - foreach
557                         } // END - if
558
559                         // Next round
560                         $GLOBALS['iteration']++;
561                         $GLOBALS['iteration_second']++;
562                         //print ('nonce=' . $GLOBALS['nonce'] . ',iteration=' . $GLOBALS['iteration'] . PHP_EOL);
563                         //print ('nonceHash=' . $nonceHash . PHP_EOL);
564                         //print ('sumNonce=' . $sumNonce . PHP_EOL);
565                         //print ('sumModula=' . $GLOBALS['sum_modula'] . PHP_EOL);
566                 } // END - while
567
568                 // If the iteration is zero, then no hash is found
569                 if ($GLOBALS['iteration'] == 0) {
570                         // Bad hash found
571                         $timeBadHashes += abs(microtime(true) - $timeHash);
572
573                         // And next round
574                         print('BAD:nonce=' . $GLOBALS['nonce'] . PHP_EOL);
575
576                         // Nothing found, so calculate new nonce
577                         calculateNonce();
578                         continue;
579                 } // END - if
580
581                 // Add amount of hashes per block (multiple-hash)
582                 $GLOBALS['hashes_block'] += $GLOBALS['iteration'] * $GLOBALS['hash_cycles'] + $GLOBALS['hash_cycles'];
583
584                 // Push found hash
585                 addFoundHash($nonceHash);
586         } // END - while
587
588         // Time taken for one
589         $timeBlock = abs(microtime(true) - $timeBlock);
590
591         // Calculate reward
592         $reward = abs($timeBlock - $timeBadHashes) / $hashrate * $GLOBALS['hashes_block'] / $GLOBALS['block_size'] * 1000;
593         print ('timeBlock=' . $timeBlock . ',timeBadHashes=' . $timeBadHashes . ',hashesPerBlock=' . $GLOBALS['hashes_block'] .',reward=' . $reward . PHP_EOL);
594
595         // Block completed
596         $GLOBALS['total_hashes'] += $GLOBALS['hashes_block'];
597         $GLOBALS['total_blocks']++;
598         $GLOBALS['hashes_block'] = 0;
599
600         // Init next block
601         $GLOBALS['found_hashes'][$GLOBALS['total_blocks']] = array();
602
603         // Calculate new nonce
604         calculateNonce();
605
606         // Add reward to total
607         $GLOBALS['total_reward'] += $reward;
608
609         // Calculate average block value
610         $blockValue = $GLOBALS['total_reward'] / $GLOBALS['total_blocks'] * $GLOBALS['total_hashes'] / ($GLOBALS['block_size'] * $GLOBALS['total_blocks']);
611
612         // Calculate reward per hour (= 3600 seconds)
613         $rewardPerHour = $GLOBALS['total_reward'] / abs(microtime(true) - START_TIME) * 3600;
614
615         print ('totalReward=' . $GLOBALS['total_reward'] . ',blockValue=' . $blockValue . ',rewardPerHour=' . $rewardPerHour . PHP_EOL);
616 } // END - while