monerowp/include/crypto/SHA3.php
2018-09-22 07:57:35 -07:00

325 lines
8.9 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php /* -*- coding: utf-8; indent-tabs-mode: t; tab-width: 4 -*-
vim: ts=4 noet ai */
/**
Streamable SHA-3 for PHP 5.2+, with no lib/ext dependencies!
Copyright © 2018 Desktopd Developers
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
@license LGPL-3+
@file
*/
/**
SHA-3 (FIPS-202) for PHP strings (byte arrays) (PHP 5.2.1+)
PHP 7.0 computes SHA-3 about 4 times faster than PHP 5.2 - 5.6 (on x86_64)
Based on the reference implementations, which are under CC-0
Reference: http://keccak.noekeon.org/
This uses PHP's native byte strings. Supports 32-bit as well as 64-bit
systems. Also for LE vs. BE systems.
*/
defined( 'ABSPATH' ) || exit;
class SHA3 {
const SHA3_224 = 1;
const SHA3_256 = 2;
const SHA3_384 = 3;
const SHA3_512 = 4;
const SHAKE128 = 5;
const SHAKE256 = 6;
const KECCAK_256 = 7;
public static function init ($type = null) {
switch ($type) {
case self::SHA3_224: return new self (1152, 448, 0x06, 28);
case self::SHA3_256: return new self (1088, 512, 0x06, 32);
case self::SHA3_384: return new self (832, 768, 0x06, 48);
case self::SHA3_512: return new self (576, 1024, 0x06, 64);
case self::SHAKE128: return new self (1344, 256, 0x1f);
case self::SHAKE256: return new self (1088, 512, 0x1f);
case self::KECCAK_256: return new self (1088, 512, 0x01, 32);
}
throw new Exception ('Invalid operation type');
}
/**
Feed input to SHA-3 "sponge"
*/
public function absorb ($data) {
if (self::PHASE_INPUT != $this->phase) {
throw new Exception ('No more input accepted');
}
$rateInBytes = $this->rateInBytes;
$this->inputBuffer .= $data;
while (strlen ($this->inputBuffer) >= $rateInBytes) {
list ($input, $this->inputBuffer) = array (
substr ($this->inputBuffer, 0, $rateInBytes)
, substr ($this->inputBuffer, $rateInBytes));
$blockSize = $rateInBytes;
for ($i = 0; $i < $blockSize; $i++) {
$this->state[$i] = $this->state[$i] ^ $input[$i];
}
$this->state = self::keccakF1600Permute ($this->state);
$this->blockSize = 0;
}
return $this;
}
/**
Get hash output
*/
public function squeeze ($length = null) {
$outputLength = $this->outputLength; // fixed length output
if ($length && 0 < $outputLength && $outputLength != $length) {
throw new Exception ('Invalid length');
}
if (self::PHASE_INPUT == $this->phase) {
$this->finalizeInput ();
}
if (self::PHASE_OUTPUT != $this->phase) {
throw new Exception ('No more output allowed');
}
if (0 < $outputLength) {
$this->phase = self::PHASE_DONE;
return $this->getOutputBytes ($outputLength);
}
$blockLength = $this->rateInBytes;
list ($output, $this->outputBuffer) = array (
substr ($this->outputBuffer, 0, $length)
, substr ($this->outputBuffer, $length));
$neededLength = $length - strlen ($output);
$diff = $neededLength % $blockLength;
if ($diff) {
$readLength = (($neededLength - $diff) / $blockLength + 1)
* $blockLength;
} else {
$readLength = $neededLength;
}
$read = $this->getOutputBytes ($readLength);
$this->outputBuffer .= substr ($read, $neededLength);
return $output . substr ($read, 0, $neededLength);
}
// internally used
const PHASE_INIT = 1;
const PHASE_INPUT = 2;
const PHASE_OUTPUT = 3;
const PHASE_DONE = 4;
private $phase = self::PHASE_INIT;
private $state; // byte array (string)
private $rateInBytes; // positive integer
private $suffix; // 8-bit unsigned integer
private $inputBuffer = ''; // byte array (string): max length = rateInBytes
private $outputLength = 0;
private $outputBuffer = '';
public function __construct ($rate, $capacity, $suffix, $length = 0) {
if (1600 != ($rate + $capacity)) {
throw new Error ('Invalid parameters');
}
if (0 != ($rate % 8)) {
throw new Error ('Invalid rate');
}
$this->suffix = $suffix;
$this->state = str_repeat ("\0", 200);
$this->blockSize = 0;
$this->rateInBytes = $rate / 8;
$this->outputLength = $length;
$this->phase = self::PHASE_INPUT;
return;
}
protected function finalizeInput () {
$this->phase = self::PHASE_OUTPUT;
$input = $this->inputBuffer;
$inputLength = strlen ($input);
if (0 < $inputLength) {
$blockSize = $inputLength;
for ($i = 0; $i < $blockSize; $i++) {
$this->state[$i] = $this->state[$i] ^ $input[$i];
}
$this->blockSize = $blockSize;
}
// Padding
$rateInBytes = $this->rateInBytes;
$this->state[$this->blockSize] = $this->state[$this->blockSize]
^ chr ($this->suffix);
if (($this->suffix & 0x80) != 0
&& $this->blockSize == ($rateInBytes - 1)) {
$this->state = self::keccakF1600Permute ($this->state);
}
$this->state[$rateInBytes - 1] = $this->state[$rateInBytes - 1] ^ "\x80";
$this->state = self::keccakF1600Permute ($this->state);
}
protected function getOutputBytes ($outputLength) {
// Squeeze
$output = '';
while (0 < $outputLength) {
$blockSize = min ($outputLength, $this->rateInBytes);
$output .= substr ($this->state, 0, $blockSize);
$outputLength -= $blockSize;
if (0 < $outputLength) {
$this->state = self::keccakF1600Permute ($this->state);
}
}
return $output;
}
/**
1600-bit state version of Keccak's permutation
*/
protected static function keccakF1600Permute ($state) {
$lanes = str_split ($state, 8);
$R = 1;
$values = "\1\2\4\10\20\40\100\200";
for ($round = 0; $round < 24; $round++) {
// θ step
$C = array ();
for ($x = 0; $x < 5; $x++) {
// (x, 0) (x, 1) (x, 2) (x, 3) (x, 4)
$C[$x] = $lanes[$x] ^ $lanes[$x + 5] ^ $lanes[$x + 10]
^ $lanes[$x + 15] ^ $lanes[$x + 20];
}
for ($x = 0; $x < 5; $x++) {
//$D = $C[($x + 4) % 5] ^ self::rotL64 ($C[($x + 1) % 5], 1);
$D = $C[($x + 4) % 5] ^ self::rotL64One ($C[($x + 1) % 5]);
for ($y = 0; $y < 5; $y++) {
$idx = $x + 5 * $y; // x, y
$lanes[$idx] = $lanes[$idx] ^ $D;
}
}
unset ($C, $D);
// ρ and π steps
$x = 1;
$y = 0;
$current = $lanes[1]; // x, y
for ($t = 0; $t < 24; $t++) {
list ($x, $y) = array ($y, (2 * $x + 3 * $y) % 5);
$idx = $x + 5 * $y;
list ($current, $lanes[$idx]) = array ($lanes[$idx]
, self::rotL64 ($current
, (($t + 1) * ($t + 2) / 2) % 64));
}
unset ($temp, $current);
// χ step
$temp = array ();
for ($y = 0; $y < 5; $y++) {
for ($x = 0; $x < 5; $x++) {
$temp[$x] = $lanes[$x + 5 * $y];
}
for ($x = 0; $x < 5; $x++) {
$lanes[$x + 5 * $y] = $temp[$x]
^ ((~ $temp[($x + 1) % 5]) & $temp[($x + 2) % 5]);
}
}
unset ($temp);
// ι step
for ($j = 0; $j < 7; $j++) {
$R = (($R << 1) ^ (($R >> 7) * 0x71)) & 0xff;
if ($R & 2) {
$offset = (1 << $j) - 1;
$shift = $offset % 8;
$octetShift = ($offset - $shift) / 8;
$n = "\0\0\0\0\0\0\0\0";
$n[$octetShift] = $values[$shift];
$lanes[0] = $lanes[0]
^ $n;
//^ self::rotL64 ("\1\0\0\0\0\0\0\0", (1 << $j) - 1);
}
}
}
return implode ($lanes);
}
protected static function rotL64_64 ($n, $offset) {
return ($n << $offset) & ($n >> (64 - $offset));
}
/**
64-bit bitwise left rotation (Little endian)
*/
protected static function rotL64 ($n, $offset) {
//$n = (binary) $n;
//$offset = ((int) $offset) % 64;
//if (8 != strlen ($n)) throw new Exception ('Invalid number');
//if ($offset < 0) throw new Exception ('Invalid offset');
$shift = $offset % 8;
$octetShift = ($offset - $shift) / 8;
$n = substr ($n, - $octetShift) . substr ($n, 0, - $octetShift);
$overflow = 0x00;
for ($i = 0; $i < 8; $i++) {
$a = ord ($n[$i]) << $shift;
$n[$i] = chr (0xff & $a | $overflow);
$overflow = $a >> 8;
}
$n[0] = chr (ord ($n[0]) | $overflow);
return $n;
}
/**
64-bit bitwise left rotation (Little endian)
*/
protected static function rotL64One ($n) {
list ($n[0], $n[1], $n[2], $n[3], $n[4], $n[5], $n[6], $n[7])
= array (
chr (((ord ($n[0]) << 1) & 0xff) ^ (ord ($n[7]) >> 7))
,chr (((ord ($n[1]) << 1) & 0xff) ^ (ord ($n[0]) >> 7))
,chr (((ord ($n[2]) << 1) & 0xff) ^ (ord ($n[1]) >> 7))
,chr (((ord ($n[3]) << 1) & 0xff) ^ (ord ($n[2]) >> 7))
,chr (((ord ($n[4]) << 1) & 0xff) ^ (ord ($n[3]) >> 7))
,chr (((ord ($n[5]) << 1) & 0xff) ^ (ord ($n[4]) >> 7))
,chr (((ord ($n[6]) << 1) & 0xff) ^ (ord ($n[5]) >> 7))
,chr (((ord ($n[7]) << 1) & 0xff) ^ (ord ($n[6]) >> 7)));
return $n;
}
}