Your script was a good attempt at this! I modified made it recursive with some very small changes.
Typically when designing a recursive function, it checks whether the state represents a complete computation, otherwise it runs some code and calls itself again. Your function was missing this part.
It also seemed to be computing the hashes incorrectly - the hashing operation is SHA256(SHA256($pair)). I refactored out the function binFlipByteOrder(), which only works on binary, so we can flip the hash before rendering it as hex.
<?php
function binFlipByteOrder($string) {
return implode('', array_reverse(str_split($string, 1)));
}
function merkleroot($txids) {
// Check for when the result is ready, otherwise recursion
if (count($txids) === 1) {
return $txids[0];
}
// Calculate the next row of hashes
$pairhashes = [];
while (count($txids) > 0) {
if (count($txids) >= 2) {
// Get first two
$pair_first = $txids[0];
$pair_second = $txids[1];
// Hash them
$pair = $pair_first.$pair_second;
$pairhashes[] = hash('sha256', hash('sha256', $pair, true), true);
// Remove those two from the array
unset($txids[0]);
unset($txids[1]);
// Re-set the indexes (the above just nullifies the values) and make a new array without the original first two slots.
$txids = array_values($txids);
}
if (count($txids) == 1) {
// Get the first one twice
$pair_first = $txids[0];
$pair_second = $txids[0];
// Hash it with itself
$pair = $pair_first.$pair_second;
$pairhashes[] = hash('sha256', hash('sha256', $pair, true), true);
// Remove it from the array
unset($txids[0]);
// Re-set the indexes (the above just nullifies the values) and make a new array without the original first two slots.
$txids = array_values($txids);
}
}
return merkleroot($pairhashes);
}
$txids = array(
'AB7ED423933FE5413DC51B1041A58FD8AF0CD70491B1CE607CB41DDDCE74A92B',
'C763E59A79C9F1E626DDF1C3E9F20F234959C457FF82918F7B24E9D18A12DB99',
'80EA3E647AEEF92973F5414E0CAA1721C7B42345B99ED161DD505ACC41F5516B',
'1B72EEFD70CE0A362EC0CB48E2213274DF3C55F9DABD5806CDC087A335498CD3',
'23E13370F6D59E2D7C7A9CA604B872312DE34A387BD7DECA2C8F4486F7E66173',
'149A098D6261B7F9359A572D797C4A41B62378836A14093912618B15644BA402',
);
$txidsBEbinary = [];
foreach ($txids as $txidBE) {
// covert to binary, then flip
$txidsBEbinary[] = binFlipByteOrder(hex2bin($txidBE));
}
$root = merkleroot($txidsBEbinary);
echo bin2hex(binFlipByteOrder($root)) . PHP_EOL;