PHP Classes

File: src/Structure/MerkleTree.php

Recommend this page to a friend!
  Packages of Scott Arciszewski   Halite   src/Structure/MerkleTree.php   Download  
File: src/Structure/MerkleTree.php
Role: Unit test script
Content type: text/plain
Description: Unit test script
Class: Halite
Perform cryptography operations with libsodium
Author: By
Last change:
Date: 10 years ago
Size: 3,039 bytes
 

Contents

Class file image Download
<?php
declare(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;
    }
}