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