*
hash algorithm identifier;
* a sibling hash value;
* concatenation direction indicator;
* level restriction (limiting the number of preceding steps) of this computation step.
*
*
* Hash chain entries are presented as GTHashEntry objects.
*
* @package tsp
*/
class GTHashChain {
private $entries = array();
/**
* Constructs a new GTHashChain instance.
*
* @throws GTException
* @param array $bytes hash chain bytes
* @param bool $checkLevel whether to check that level bytes are in increasing order
*/
private function __construct(array $bytes, $checkLevel) {
if (empty($bytes)) {
throw new GTException("parameter bytes is required");
}
if (!is_array($bytes)) {
throw new GTException("parameter bytes must be an array of bytes");
}
$previousLevel = -1;
for ($i = 0; $i < count($bytes);) {
$algorithm = GTHashAlgorithm::getByGtid($bytes[$i++]);
if ($i >= count($bytes)) {
throw new GTException("Invalid chain: not enough bytes");
}
$direction = $bytes[$i++];
if ($i >= count($bytes)) {
throw new GTException("Invalid chain: not enough bytes");
}
if ($direction != 0 && $direction != 1) {
throw new GTException("invalid hash step direction: {$direction}");
}
$siblingAlgorithm = GTHashAlgorithm::getByGtid($bytes[$i++]);
$siblingHash = new GTDataHash($siblingAlgorithm, array_slice($bytes, $i, $siblingAlgorithm->getLength()));
$i += $siblingAlgorithm->getLength();
if ($i >= count($bytes)) {
throw new GTException("Invalid chain: not enough bytes");
}
$level = $bytes[$i++] & 0xFF;
if ($checkLevel && $previousLevel >= $level) {
throw new GTException("invalid hash step level: {$level}");
}
$previousLevel = $level;
array_push($this->entries, new GTHashEntry($algorithm, $direction, $siblingHash, $level));
}
}
/**
* Computes the result of passing the given bytes through this hash chain.
*
* @throws GTException
* @param array $bytes bytes to compute chain output for
* @return array chain output, the final hash value
*/
public function computeOutput(array $bytes) {
if (empty($bytes)) {
throw new GTException("parameter bytes is required");
}
if (!is_array($bytes)) {
throw new GTException("parameter bytes must be an array of bytes");
}
$output = $bytes;
foreach ($this->entries as $entry) {
$output = $entry->computeOutput($output);
}
return $output;
}
/**
* Checks that past entries match in this history chain and the other provided history chain.
*
* Past history chain entries are those which have a 0 direction.
*
* @param GTHashChain $hashChain history chain to compare this chain entries with
* @return bool true if all past entries match, false otherwise
*/
public function checkPastEntries(GTHashChain $hashChain) {
$iter1 = new ArrayIterator($this->entries);
$iter2 = new ArrayIterator($hashChain->entries);
while ($iter1->valid()) {
$entry = null;
do {
$entry = $iter1->current();
$iter1->next();
} while ($iter1->valid() && $entry->getDirection() == 1);
if ($entry->getDirection() == 0 && !$iter2->valid()) {
return false;
}
$other = null;
do {
$other = $iter2->current();
$iter2->next();
} while ($iter2->valid() && $other->getDirection() == 1);
if ($entry->getDirection() == 1 && $other->getDirection() == 1) {
return true;
} else if ($entry != $other) {
return false;
}
}
return true;
}
/**
* Computes location ID of this chain.
*
* This computation is only sensible for location chains.
*
* @return GTBigInteger location ID computed on this location chain
*/
public function computeLocationId() {
// Skip: machine bits + slot bits
$topSkip = 3 + 3;
$nationalSkip = 3 + 2;
$stateSkip = 2 + 2;
// Level: depth + machine bits + slot bits - 2
$topLevel = 60 + $topSkip - 2;
$nationalLevel = 39 + $nationalSkip - 2;
$stateLevel = 19 + $stateSkip - 2;
$locationId = new GTBigInteger(0);
$i = count($this->entries) - 1;
$i--; // skip hasher bit
// calculate national id
$idNational = 0;
while (true) {
$entry = $this->entries[$i--];
if ($entry->getLevel() > $topLevel) {
$idNational = 2 * $idNational + (1 - $entry->getDirection());
} else {
break;
}
}
$locationId = $locationId->shiftLeft(16)->bitOr(new GTBigInteger($idNational));
// Skip machine and slot bits of top-level aggregator
$i -= $topSkip;
// Calculate state ID
$idState = 0;
while (true) {
$entry = $this->entries[$i--];
if ($entry->getLevel() > $nationalLevel) {
$idState = 2 * $idState + (1 - $entry->getDirection());
} else {
break;
}
}
$locationId = $locationId->shiftLeft(16)->bitOr(new GTBigInteger($idState));
// Skip machine and slot bits of national-level aggregator
$i -= $nationalSkip;
// Calculate local ID
$idLocal = 0;
while (true) {
$entry = $this->entries[$i--];
if ($entry->getLevel() > $stateLevel) {
$idLocal = 2 * $idLocal + (1 - $entry->getDirection());
} else {
break;
}
}
$locationId = $locationId->shiftLeft(16)->bitOr(new GTBigInteger($idLocal));
// Skip machine and slot bits of state-level aggregator
$i -= $stateSkip;
// Calculate client ID
$idClient = 0;
while ($i > 0) {
$entry = $this->entries[$i--];
$idClient = 2 * $idClient + (1 - $entry->getDirection());
}
$locationId = $locationId->shiftLeft(16)->bitOr(new GTBigInteger($idClient));
return $locationId;
}
/**
* Computes history ID of this hash chain.
*
* History ID is the number of seconds from 1970-01-01 00:00:00 UTC to
* the time corresponding to the starting position of this hash chain
* in the GuardTime calendar tree.
*
* This computation is only sensible for history chains.
*
* @param string $publicationId history ID corresponding to the root of the
* calendar tree from which the history chain was extracted.
*
* @return GTBigInteger history ID computed on this history chain
*/
public function computeHistoryId($publicationId) {
$N = $publicationId->add(new GTBigInteger(1));
$m = 0; // number of 1 bits in N
$bits = $N->toBits();
for ($i = 0; $i < strlen($bits); $i++) {
if ($bits{$i} == '1') {
$m++;
}
}
$hashChainLength = count($this->entries);
$hashChainDirs = new GTBigInteger(0);
$i = $hashChainLength;
while ($i > 0) {
$i--;
$hashChainDirs = $hashChainDirs->shiftLeft(1);
$entry = $this->entries[$i];
if ($entry->getDirection() == 1) {
$hashChainDirs = $hashChainDirs->bitOr(new GTBigInteger(1));
}
}
// number of leading zeroes in $hashChainDirs
$bits = $hashChainDirs->toBits();
// pad to 64 bit
while (strlen($bits) % 64 != 0) {
$bits = '0' . $bits;
}
$z = 0;
for ($i = 0; $i < strlen($bits); $i++) {
if ($bits{$i} == '0') {
$z++;
} else {
break;
}
}
// Get leading zeros within `hashChainLen` number of last bits
$z = $z - (64 - $hashChainLength);
$count = 0;
if ($z + 1 > $m) {
$hashChainLength -= $m - 1;
$count = 1;
} else {
$hashChainLength -= $z + 1;
$count = $m - $z;
}
$mask = new GTBigInteger(0x1);
$zero = new GTBigInteger(0x0);
$i = 0;
while ($i < $count && $N->comp($zero) > 0) {
if ($N->bitAnd($mask)->comp($mask) == 0) {
$N = $N->bitXOr($mask);
$i++;
}
$mask = $mask->shiftLeft(1);
}
$hashChainDirs = $hashChainDirs->bitNot();
$mask = new GTBigInteger(0x1);
$n = new GTBigInteger(0);
$i = 0;
while ($i < $hashChainLength) {
if ($hashChainDirs->bitAnd($mask)->comp($mask) == 0) {
$n = $n->add($mask);
}
$mask = $mask->shiftLeft(1);
$i++;
}
$result = new GTBigInteger($n);
$result = $result->add($N);
return $result;
}
/**
* Builds a new location hash chain out of the specified hash chain bytes.
*
* Hash chain bytes is a binary representation of a hash chain extracted
* from the timestamp.
*
* @static
* @param array $bytes byte array containing the hash chain bytes
* @return GTHashChain hash chain object
*/
public static function getLocationInstance(array $bytes) {
return new GTHashChain($bytes, true);
}
/**
* Builds a new history hash chain out of the specified hash chain bytes.
*
* Hash chain bytes is a binary representation of a hash chain extracted
* from the timestamp.
*
* @static
* @param array $bytes byte array containing the hash chain bytes
* @return GTHashChain hash chain object
*/
public static function getHistoryInstance(array $bytes) {
return new GTHashChain($bytes, false);
}
}
?>