30 seconds every flush is okay.
[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, 12));
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(CHECK_POINT, $GLOBALS['total_blocks'] . ':' . $GLOBALS['total_reward'] . ':' . $GLOBALS['total_hashes'] . ':' . $GLOBALS['hash_cycles'] . ':' . base64_encode($GLOBALS['nonce']) . ':' . $hash . ':' . $GLOBALS['root_hash'] . ':' . base64_encode(gzcompress(serialize($GLOBALS['found_hashes']))));
211
212         // Set time
213         $GLOBALS['time_flush'] = microtime(TRUE);
214         print ('FLUSHING: Took ' . ($GLOBALS['time_flush'] - $timer) . ' seconds.' . PHP_EOL);
215 }
216
217 /**
218  * Adds a found hash and flushes the checkpoint file
219  *
220  * @param       $hash   Hash to save
221  */
222 function addFoundHash ($hash) {
223         // Increment counter
224         $GLOBALS['current_hashes']++;
225
226         // Add hash to array
227         array_push($GLOBALS['found_hashes'][$GLOBALS['total_blocks']], array(
228                 'modula_hash'  => $GLOBALS['modula_hash'],
229                 'genesis_hash' => $GLOBALS['genesis_hash'],
230                 'root_hash'    => $GLOBALS['root_hash'],
231                 'nonce'        => $GLOBALS['nonce'],
232                 'iter'         => $GLOBALS['iteration'],
233                 'hashes_block' => $GLOBALS['hashes_block'],
234                 'hash_cycles'  => $GLOBALS['hash_cycles'],
235                 'nonce_hash'   => $hash
236         ));
237
238         // Found hash:
239         print ('FOUND: hash=' . $hash . ',nonce=' . $GLOBALS['nonce'] . ',current_hashes=' . $GLOBALS['current_hashes'] . PHP_EOL);
240
241         // Set time as a new hash was found
242         $GLOBALS['found_time'] = microtime(TRUE);
243
244         // Flush check-point file after new hash is found
245         flushCheckPointFile($hash);
246
247         // Use nonceHash as next modula hash
248         setModulaHash($hash);
249 }
250
251 /**
252  * Initializes nonce
253  *
254  * @return      void
255  */
256 function initNonce () {
257         $GLOBALS['nonce'] = 1 / (mt_rand() ^ pi());
258         print (__FUNCTION__ . ': nonce=' . $GLOBALS['nonce'] . PHP_EOL);
259 }
260
261 /**
262  * Sets modula hash and calculates sum of it
263  *
264  * @param       $hash   Hash to set as "modula hash"
265  * @return      void
266  */
267 function setModulaHash ($hash) {
268         $GLOBALS['modula_hash']  = $hash;
269         $GLOBALS['sum_modula'] = calculateSumFromHash($GLOBALS['modula_hash']);
270 }
271
272 /*
273  * Calculate "genesis" hashes, please note that these "genesis strings" are now
274  * known to the public as you can read them here in source code and therefore I
275  * will not use them for the real genesis hashes.
276  */
277 $gensisHashes = array(
278         // A famous quote from Deus Ex 2 - Invisible War
279         multiplehashString('"Informations must be free." - AI Helios from Deus Ex'),
280         // My name + URL of my first StatusNet instance
281         multipleHashString('Roland Haeder, https://status.mxchange.org'),
282         // A famous quote from Linus Torwalds
283         multipleHashString('"Software is like sex. Its better when its free." - Linus Torwalds'),
284         // Well ...
285         multipleHashString('September 11 is a big lie.'),
286
287         // GNU is not Uni*
288         multipleHashString('GNU is Not Uni*.'),
289         // WINE is not an emulator
290         multipleHashString('WINE Is Not an Emulator.'),
291         // FlightGear - Fly free!
292         multipleHashString('FlightGear - Fly free!'),
293         // Quote from Linus Torwalds
294         multipleHashString('Your code is shit.. your argument is shit.'),
295 );
296
297 // Calculate "modula hash" from 1st/4th and 2nd/3rd
298 $modulaHashes = array(
299         // "Block" 0
300         modulaHash($gensisHashes[0], $gensisHashes[3]),
301         modulaHash($gensisHashes[1], $gensisHashes[2]),
302
303         // "Block" 1
304         modulaHash($gensisHashes[4], $gensisHashes[7]),
305         modulaHash($gensisHashes[5], $gensisHashes[6]),
306 );
307
308 // Calculate "sqrt hash"
309 $sqrtHashes = array(
310         sqrtHash($modulaHashes[0], $modulaHashes[1]),
311         sqrtHash($modulaHashes[2], $modulaHashes[3])
312 );
313
314 // Calulcate modula hash
315 setModulaHash(multipleHashString(modulaHash($sqrtHashes[0], $sqrtHashes[1])));
316
317 // This is also the "genesis" hash and first root hash
318 $GLOBALS['genesis_hash'] = $GLOBALS['modula_hash'];
319 $GLOBALS['root_hash']    = $GLOBALS['modula_hash'];
320
321 // Output results
322 print ('hashes=' . print_r($gensisHashes, TRUE));
323 print ('modulaHashes=' . print_r($modulaHashes, TRUE));
324 print ('sqrtHashes=' . print_r($sqrtHashes, TRUE));
325 print ('modulaHash=' . $GLOBALS['modula_hash'] . PHP_EOL);
326
327 // Total reward + hashes
328 $GLOBALS['total_reward']   = 0;
329 $GLOBALS['total_hashes']   = 0;
330 $GLOBALS['total_blocks']   = 0;
331 $GLOBALS['current_hashes'] = 0;
332 $GLOBALS['found_time']     = microtime(TRUE);
333
334 // Is the check point there?
335 if (is_readable(CHECK_POINT)) {
336         // Then load it
337         $checkPoint = file_get_contents(CHECK_POINT);
338
339         // Explode it
340         $data = explode(':', $checkPoint);
341
342         // Assert on count
343         assert(count($data) == 8);
344
345         // 1st element is nonce, 2nd hash, 3rd found hashes
346         $GLOBALS['total_blocks'] = $data[0];
347         $GLOBALS['total_reward'] = $data[1];
348         $GLOBALS['total_hashes'] = $data[2];
349         $GLOBALS['hash_cycles']  = intval($data[3]);
350         $GLOBALS['nonce']        = base64_decode($data[4]);
351         $GLOBALS['root_hash']    = $data[6];
352         $GLOBALS['found_hashes'] = unserialize(gzuncompress(base64_decode($data[7])));
353
354         // Set modula hash
355         setModulaHash($data[5]);
356 } else {
357         // Create nonce (small)
358         initNonce();
359 }
360
361 // Output again
362 print ('modulaHash=' . $GLOBALS['modula_hash'] . PHP_EOL);
363 print ('nonce=' . $GLOBALS['nonce'] . PHP_EOL);
364 print ('found=' . count($GLOBALS['found_hashes'][$GLOBALS['total_blocks']]) . PHP_EOL);
365
366 // Start "mining"
367 while (TRUE) {
368         // Init hash-per-block counter and hashrate
369         $GLOBALS['hashes_block'] = 0;
370         $hashrate = 0;
371
372         // Wait for block_size iterations (= found hashes). This is one block
373         $timeBlock   = microtime(TRUE);
374         $timeDisplay = $timeBlock;
375         $GLOBALS['time_flush'] = $timeBlock;
376
377         // Time waited for a good block again (no iteration)
378         $timeBadHashes = 0;
379
380         while (count($GLOBALS['found_hashes'][$GLOBALS['total_blocks']]) <= $GLOBALS['block_size']) {
381                 // Create hash from modulaHash ("genesis hash") and nonce
382                 $nonceHash = multipleHashString($GLOBALS['nonce'] . $GLOBALS['modula_hash']);
383
384                 // Calculate sums
385                 $sumNonce  = calculateSumFromHash($nonceHash);
386
387                 // Init counter
388                 $GLOBALS['iteration'] = 0;
389                 $GLOBALS['iteration_second'] = 0;
390
391                 // Now start the "mining" ...
392                 $timeHash = microtime(TRUE);
393                 while ($sumNonce < $GLOBALS['sum_modula']) {
394                         // Calculate new nonce
395                         calculateNonce();
396
397                         // And hash again
398                         $nonceHash = multipleHashString($GLOBALS['nonce'] . $GLOBALS['modula_hash']);
399
400                         // Calculate sums
401                         $sumNonce  = calculateSumFromHash($nonceHash);
402
403                         // Time spend in loop
404                         $testTime = abs(microtime(TRUE) - $timeDisplay);
405
406                         // Calculate hashrate/sec
407                         $hashrate = 1 / $testTime * $GLOBALS['iteration_second'] * $GLOBALS['hash_cycles'];
408
409                         // Only every second
410                         if ($testTime >= 1) {
411                                 // Display hash rate
412                                 print ('hashrate=' . round($hashrate) . ' hashes/sec,iterSecond=' . $GLOBALS['iteration_second'] . ' iterations/sec' . PHP_EOL);
413
414                                 // Reset timer
415                                 $timeDisplay = microtime(TRUE);
416                                 $GLOBALS['iteration_second']  = 0;
417                         } // END - if
418
419                         // Time spend from last flush
420                         $testTime = abs(microtime(TRUE) - $GLOBALS['time_flush']);
421
422                         // Only once per 10 seconds
423                         if ($testTime >= $GLOBALS['flush_file_time']) {
424                                 // Flush check-point file
425                                 flushCheckPointFile($GLOBALS['modula_hash']);
426                         } // END - if
427
428                         // Time spend from last found block
429                         $testTime = abs(microtime(TRUE) - $GLOBALS['found_time']);
430
431                         // Is the last found time to far away?
432                         if ($testTime >= $GLOBALS['restart_search_time']) {
433                                 // Count all root (genesis) hashes
434                                 $rootHashes = array();
435                                 foreach ($GLOBALS['found_hashes'] as $block) {
436                                         // "Walk" through all blocks
437                                         foreach ($block as $hash) {
438                                                 if (!isset($hash['root_hash'])) {
439                                                         // Bad file
440                                                         die('INCONSISTENCY: hash=' . print_r($hash, TRUE));
441                                                 } // END - if
442
443                                                 if (isset($rootHashes[$hash['root_hash']])) {
444                                                         // Count up
445                                                         $rootHashes[$hash['root_hash']]++;
446                                                 } else {
447                                                         // First entry found
448                                                         $rootHashes[$hash['root_hash']] = 1;
449                                                 }
450                                         } // END - foreach
451                                 } // END - foreach
452
453                                 // Find best root hash
454                                 $bestRootHash = '';
455                                 $bestRootCount = 0;
456                                 foreach ($rootHashes as $hash => $count) {
457                                         // Debug message
458                                         //* NOISY-DEBUG: */ print ('hash=' . $hash . ',count=' . $count . ',bestRootHash=' . $bestRootHash . ',bestRootCount=' . $bestRootCount . PHP_EOL);
459
460                                         // Is a better one found?
461                                         if ($count > $bestRootCount) {
462                                                 // Remember it
463                                                 $bestRootHash  = $hash;
464                                                 $bestRootCount = $count;
465                                         } // END - if
466                                 } // END - foreach
467
468                                 // Output message
469                                 print ('bestRootHash=' . $bestRootHash . ',bestRootCount=' . $bestRootCount . PHP_EOL);
470
471                                 // Search for latest best root hash
472                                 foreach ($GLOBALS['found_hashes'] as $block) {
473                                         // "Walk" through whole block and search for first appearance of best root hash
474                                         foreach ($block as $idx => $hash) {
475                                                 // Is the root hash there?
476                                                 if ($hash['root_hash'] == $bestRootHash) {
477                                                         // Set found modula hash as new root and current modula hash
478                                                         $GLOBALS['root_hash']   = $hash['nonce_hash'];
479                                                         setModulaHash($hash['nonce_hash']);
480                                                         print ('idx=' . $idx . ',modulaHash=' . $GLOBALS['root_hash'] . ' - Is now new root hash!' . PHP_EOL);
481
482                                                         // Reset "found time" (when a hash was found)
483                                                         $GLOBALS['found_time'] = microtime(TRUE);
484
485                                                         // Re-initialize nonce
486                                                         initNonce();
487
488                                                         // Abort search
489                                                         break;
490                                                 } // END - if
491                                         } // END - for
492                                 } // END - foreach
493                         } // END - if
494
495                         // Next round
496                         $GLOBALS['iteration']++;
497                         $GLOBALS['iteration_second']++;
498                         //print ('nonce=' . $GLOBALS['nonce'] . ',iteration=' . $GLOBALS['iteration'] . PHP_EOL);
499                         //print ('nonceHash=' . $nonceHash . PHP_EOL);
500                         //print ('sumNonce=' . $sumNonce . PHP_EOL);
501                         //print ('sumModula=' . $GLOBALS['sum_modula'] . PHP_EOL);
502                 } // END - while
503
504                 // If the iteration is zero, then no hash is found
505                 if ($GLOBALS['iteration'] == 0) {
506                         // Bad hash found
507                         $timeBadHashes += abs(microtime(TRUE) - $timeHash);
508
509                         // And next round
510                         print('BAD:nonce=' . $GLOBALS['nonce'] . PHP_EOL);
511
512                         // Nothing found, so calculate new nonce
513                         calculateNonce();
514                         continue;
515                 } // END - if
516
517                 // Add amount of hashes per block (multiple-hash)
518                 $GLOBALS['hashes_block'] += $GLOBALS['iteration'] * $GLOBALS['hash_cycles'] + $GLOBALS['hash_cycles'];
519
520                 // Push found hash
521                 addFoundHash($nonceHash);
522         } // END - while
523
524         // Time taken for one
525         $timeBlock = abs(microtime(TRUE) - $timeBlock);
526
527         // Calculate reward
528         $reward = abs($timeBlock - $timeBadHashes) / $hashrate * $GLOBALS['hashes_block'] / $GLOBALS['block_size'] * 1000;
529         print ('timeBlock=' . $timeBlock . ',timeBadHashes=' . $timeBadHashes . ',hashesPerBlock=' . $GLOBALS['hashes_block'] .',reward=' . $reward . PHP_EOL);
530
531         // Block completed
532         $GLOBALS['total_hashes'] += $GLOBALS['hashes_block'];
533         $GLOBALS['total_blocks']++;
534         $GLOBALS['hashes_block'] = 0;
535
536         // Init next block
537         $GLOBALS['found_hashes'][$GLOBALS['total_blocks']] = array();
538
539         // Calculate new nonce
540         calculateNonce();
541
542         // Add reward to total
543         $GLOBALS['total_reward'] += $reward;
544
545         // Calculate average block value
546         $blockValue = $GLOBALS['total_reward'] / $GLOBALS['total_blocks'] * $GLOBALS['total_hashes'] / ($GLOBALS['block_size'] * $GLOBALS['total_blocks']);
547
548         // Calculate reward per hour (= 3600 seconds)
549         $rewardPerHour = $GLOBALS['total_reward'] / abs(microtime(TRUE) - START_TIME) * 3600;
550
551         print ('totalReward=' . $GLOBALS['total_reward'] . ',blockValue=' . $blockValue . ',rewardPerHour=' . $rewardPerHour . PHP_EOL);
552 } // END - while
553
554 // [EOF]
555 ?>