| 
<?phpdeclare(strict_types=1);
 namespace ParagonIE\Halite\Structure;
 
 /**
 * An implementation of a Merkle hash tree, built on the BLAKE2b hash function
 * (provided by libsodium)
 */
 class MerkleTree
 {
 const MERKLE_LEAF =   "\x01";
 const MERKLE_BRANCH = "\x00";
 
 private $root = '';
 private $nodes = [];
 
 /**
 * Instantiate a Merkle tree
 *
 * @param Node[] $nodes
 */
 public function __construct(Node ...$nodes)
 {
 $this->nodes = $nodes;
 $this->root = $this->calculateRoot();
 }
 
 /**
 *
 * @param bool $raw - Do we want a raw string instead of a hex string?
 *
 * @return string
 */
 public function getRoot(bool $raw = false): string
 {
 return $raw
 ? $this->root
 : \Sodium\bin2hex($this->root);
 }
 
 /**
 * Merkle Trees are immutable. Return a replacement with extra nodes.
 *
 * @return MerkleTree
 */
 public function getExpandedTree(Node ...$nodes)
 {
 $thisTree = $this->nodes;
 foreach ($nodes as $node) {
 $thisTree []= $node;
 }
 return new MerkleTree(...$thisTree);
 }
 
 /**
 * Calculate the Merkle root, taking care to distinguish between
 * leaves and branches (0x01 for the nodes, 0x00 for the branches)
 * to protect against second-preimage attacks
 *
 * @return string
 */
 protected function calculateRoot(): string
 {
 $size = \count($this->nodes);
 $order = self::getSizeRoundedUp($size);
 $hash = [];
 $debug = [];
 // Population (Use self::MERKLE_LEAF as a prefix)
 for ($i = 0; $i < $order; ++$i) {
 if ($i >= $size) {
 $hash[$i] = self::MERKLE_LEAF . $this->nodes[$size - 1]->getHash(true);
 $debug[$i] = \Sodium\bin2hex($hash[$i]);
 } else {
 $hash[$i] = self::MERKLE_LEAF . $this->nodes[$i]->getHash(true);
 $debug[$i] = \Sodium\bin2hex($hash[$i]);
 }
 }
 // Calculation (Use self::MERKLE_BRANCH as a prefix)
 do {
 $tmp = [];
 $j = 0;
 for ($i = 0; $i < $order; $i += 2) {
 if (empty($hash[$i + 1])) {
 $tmp[$j] = \Sodium\crypto_generichash(self::MERKLE_BRANCH . $hash[$i] . $hash[$i]);
 } else {
 $tmp[$j] = \Sodium\crypto_generichash(self::MERKLE_BRANCH . $hash[$i] . $hash[$i + 1]);
 }
 ++$j;
 }
 $hash = $tmp;
 $order >>= 1;
 } while ($order > 1);
 // We should only have one value left:t
 return \array_shift($hash);
 }
 
 /**
 * Let's go ahead and round up to the nearest mutliple of 2
 *
 * @return int
 */
 public static function getSizeRoundedUp(int $inputSize): int
 {
 $order = 1;
 while($order < $inputSize) {
 $order <<= 1;
 }
 return $order;
 }
 }
 
 |