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