<?php

function word_by_index($index, $dicts) {
    $sizes = array_map(function($val){return count($val);}, $dicts); // получаем размеры каждого словаря
    $result = '';
    foreach($sizes as $key=>$size) {
        $result .= $dicts[$key][$index % $size]; // сцепляем слово из частей
        $index = floor($index / $size);
    }
    return $result;
}

function R($hash, $dicts, $total) {
    $index = gmp_intval(gmp_mod(gmp_init($hash, 16), $total));
    return word_by_index($index, $dicts); 
}

function make_chain($start, $length, $dicts, $total) {
    $chain['start'] = $start;
    for($i = 0; $i < $length; ++$i) {
        $hash = md5($start);                    // <-- сюда вставьте нужную хэш-функцию
        // echo ">>> $hash : $start\n";            // диагностическое сообщение
        $start = R($hash, $dicts, $total);
    }
    $chain['end'] = $hash;
    echo "Chain from ${chain['start']} to ${chain['end']} is ready.\n"; // диагностическое сообщение
    return $chain;
}

function make_chains($count, $length, $dicts) {
    $sizes = array_map(function($val){return count($val);}, $dicts); // получаем размеры каждого словаря
    $total = array_reduce($sizes, function($carry,$item){return $carry*$item;}, 1); // произведение размеров словарей
    $chains = [];
    mt_srand();
    for($i = 0; $i < $count; ++$i) {
        $word = word_by_index(mt_rand(0, $total - 1), $dicts);  // начинаем цепочку с псевдослучайного слова
        $chain = make_chain($word, $length, $dicts, $total);
        $hash = $chain['end'];                               // используем конец найденной цепочки как индекс для быстрого поиска
        if(!isset($chains[$hash])) $chains[$hash] = [];      // если такого хэша не было в корзине, инициализируем её
        if(!in_array($chain['start'], $chains[$hash])) {     // проверяем на дубли
            $chains[$hash][] = $chain['start'];              // добавляем начало цепочки в корзину
        }
    }
    return $chains;
}

function find_hash_in_basket($needle, $haystack_start, $haystack_end, $dicts, $total) {
    echo "Роемся в цепочке от $haystack_start до $haystack_end.\n";       // диагностическое сообщение
    $current_word = $haystack_start;
    do {
        $current_hash = md5($current_word);         // <-- сюда вставьте нужную хэш-функцию
        if($current_hash === $needle) {
             return $current_word;                  // нашли слово, хэш от которого равен заданному
        }
        $current_word = R($current_hash, $dicts, $total);  // роем в глубину
    } while($current_hash !== $haystack_end);
    return false; // не нашли
}

function search_hash($hash, $dicts, $chains, $length) {
    $sizes = array_map(function($val){return count($val);}, $dicts); // получаем размеры каждого словаря
    $total = array_reduce($sizes, function($carry,$item){return $carry*$item;}, 1); // произведение размеров словарей
    $current_hash = $hash;
    for($i = 0; $i < $length; ++$i) {
          if(isset($chains[$current_hash])) {                // нашли хэш в одной из корзин
              echo "Лезем в корзину $current_hash.\n";       // диагностическое сообщение
              foreach($chains[$current_hash] as $start) {    // роемся в корзине
                  $result = find_hash_in_basket($hash, $start, $current_hash, $dicts, $total); // пытаемся найти в каждой из цепочек корзины
                  if($result) {
                      return $result;                        // конец поиска
                  }
              }
          }
          $next_word = R($current_hash, $dicts, $total);             // копаем в глубину
          $current_hash = md5($next_word);
    }
    return false; // не нашли
}

///////////////////// ПРИМЕР //////////////////////////////////

$dicts= array(
      array('свино',  'овце', 'тигро', 'косатко', 'зубро', 'волко', 'кото'),
      array('собака', 'бык',  'лев',   'дельфин', 'бизон')
);

$chains = make_chains(15, 15, $dicts);
echo "Радужные таблицы готовы.\n";
var_dump($chains);

$hash = '360629d3cf05cee0240a23e1251c58a0';
echo "Пытаемся обратить $hash.\n";
$word = search_hash($hash, $dicts, $chains, 15);
echo "$hash is reversed to $word.\n";
