summaryrefslogtreecommitdiffstats
path: root/vendor/paragonie/sodium_compat/src/Core/Util.php
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--vendor/paragonie/sodium_compat/src/Core/Util.php1889
1 files changed, 947 insertions, 942 deletions
diff --git a/vendor/paragonie/sodium_compat/src/Core/Util.php b/vendor/paragonie/sodium_compat/src/Core/Util.php
index 865f544..73e463f 100644
--- a/vendor/paragonie/sodium_compat/src/Core/Util.php
+++ b/vendor/paragonie/sodium_compat/src/Core/Util.php
@@ -1,942 +1,947 @@
-<?php
-
-if (class_exists('ParagonIE_Sodium_Core_Util', false)) {
- return;
-}
-
-/**
- * Class ParagonIE_Sodium_Core_Util
- */
-abstract class ParagonIE_Sodium_Core_Util
-{
- /**
- * @param int $integer
- * @param int $size (16, 32, 64)
- * @return int
- */
- public static function abs($integer, $size = 0)
- {
- /** @var int $realSize */
- $realSize = (PHP_INT_SIZE << 3) - 1;
- if ($size) {
- --$size;
- } else {
- /** @var int $size */
- $size = $realSize;
- }
-
- $negative = -(($integer >> $size) & 1);
- return (int) (
- ($integer ^ $negative)
- +
- (($negative >> $realSize) & 1)
- );
- }
-
- /**
- * Convert a binary string into a hexadecimal string without cache-timing
- * leaks
- *
- * @internal You should not use this directly from another application
- *
- * @param string $binaryString (raw binary)
- * @return string
- * @throws TypeError
- */
- public static function bin2hex($binaryString)
- {
- /* Type checks: */
- if (!is_string($binaryString)) {
- throw new TypeError('Argument 1 must be a string, ' . gettype($binaryString) . ' given.');
- }
-
- $hex = '';
- $len = self::strlen($binaryString);
- for ($i = 0; $i < $len; ++$i) {
- /** @var array<int, int> $chunk */
- $chunk = unpack('C', $binaryString[$i]);
- /** @var int $c */
- $c = $chunk[1] & 0xf;
- /** @var int $b */
- $b = $chunk[1] >> 4;
- $hex .= pack(
- 'CC',
- (87 + $b + ((($b - 10) >> 8) & ~38)),
- (87 + $c + ((($c - 10) >> 8) & ~38))
- );
- }
- return $hex;
- }
-
- /**
- * Convert a binary string into a hexadecimal string without cache-timing
- * leaks, returning uppercase letters (as per RFC 4648)
- *
- * @internal You should not use this directly from another application
- *
- * @param string $bin_string (raw binary)
- * @return string
- * @throws TypeError
- */
- public static function bin2hexUpper($bin_string)
- {
- $hex = '';
- $len = self::strlen($bin_string);
- for ($i = 0; $i < $len; ++$i) {
- /** @var array<int, int> $chunk */
- $chunk = unpack('C', $bin_string[$i]);
- /**
- * Lower 16 bits
- *
- * @var int $c
- */
- $c = $chunk[1] & 0xf;
-
- /**
- * Upper 16 bits
- * @var int $b
- */
- $b = $chunk[1] >> 4;
-
- /**
- * Use pack() and binary operators to turn the two integers
- * into hexadecimal characters. We don't use chr() here, because
- * it uses a lookup table internally and we want to avoid
- * cache-timing side-channels.
- */
- $hex .= pack(
- 'CC',
- (55 + $b + ((($b - 10) >> 8) & ~6)),
- (55 + $c + ((($c - 10) >> 8) & ~6))
- );
- }
- return $hex;
- }
-
- /**
- * Cache-timing-safe variant of ord()
- *
- * @internal You should not use this directly from another application
- *
- * @param string $chr
- * @return int
- * @throws SodiumException
- * @throws TypeError
- */
- public static function chrToInt($chr)
- {
- /* Type checks: */
- if (!is_string($chr)) {
- throw new TypeError('Argument 1 must be a string, ' . gettype($chr) . ' given.');
- }
- if (self::strlen($chr) !== 1) {
- throw new SodiumException('chrToInt() expects a string that is exactly 1 character long');
- }
- /** @var array<int, int> $chunk */
- $chunk = unpack('C', $chr);
- return (int) ($chunk[1]);
- }
-
- /**
- * Compares two strings.
- *
- * @internal You should not use this directly from another application
- *
- * @param string $left
- * @param string $right
- * @param int $len
- * @return int
- * @throws SodiumException
- * @throws TypeError
- */
- public static function compare($left, $right, $len = null)
- {
- $leftLen = self::strlen($left);
- $rightLen = self::strlen($right);
- if ($len === null) {
- $len = max($leftLen, $rightLen);
- $left = str_pad($left, $len, "\x00", STR_PAD_RIGHT);
- $right = str_pad($right, $len, "\x00", STR_PAD_RIGHT);
- }
-
- $gt = 0;
- $eq = 1;
- $i = $len;
- while ($i !== 0) {
- --$i;
- $gt |= ((self::chrToInt($right[$i]) - self::chrToInt($left[$i])) >> 8) & $eq;
- $eq &= ((self::chrToInt($right[$i]) ^ self::chrToInt($left[$i])) - 1) >> 8;
- }
- return ($gt + $gt + $eq) - 1;
- }
-
- /**
- * If a variable does not match a given type, throw a TypeError.
- *
- * @param mixed $mixedVar
- * @param string $type
- * @param int $argumentIndex
- * @throws TypeError
- * @throws SodiumException
- * @return void
- */
- public static function declareScalarType(&$mixedVar = null, $type = 'void', $argumentIndex = 0)
- {
- if (func_num_args() === 0) {
- /* Tautology, by default */
- return;
- }
- if (func_num_args() === 1) {
- throw new TypeError('Declared void, but passed a variable');
- }
- $realType = strtolower(gettype($mixedVar));
- $type = strtolower($type);
- switch ($type) {
- case 'null':
- if ($mixedVar !== null) {
- throw new TypeError('Argument ' . $argumentIndex . ' must be null, ' . $realType . ' given.');
- }
- break;
- case 'integer':
- case 'int':
- $allow = array('int', 'integer');
- if (!in_array($type, $allow)) {
- throw new TypeError('Argument ' . $argumentIndex . ' must be an integer, ' . $realType . ' given.');
- }
- $mixedVar = (int) $mixedVar;
- break;
- case 'boolean':
- case 'bool':
- $allow = array('bool', 'boolean');
- if (!in_array($type, $allow)) {
- throw new TypeError('Argument ' . $argumentIndex . ' must be a boolean, ' . $realType . ' given.');
- }
- $mixedVar = (bool) $mixedVar;
- break;
- case 'string':
- if (!is_string($mixedVar)) {
- throw new TypeError('Argument ' . $argumentIndex . ' must be a string, ' . $realType . ' given.');
- }
- $mixedVar = (string) $mixedVar;
- break;
- case 'decimal':
- case 'double':
- case 'float':
- $allow = array('decimal', 'double', 'float');
- if (!in_array($type, $allow)) {
- throw new TypeError('Argument ' . $argumentIndex . ' must be a float, ' . $realType . ' given.');
- }
- $mixedVar = (float) $mixedVar;
- break;
- case 'object':
- if (!is_object($mixedVar)) {
- throw new TypeError('Argument ' . $argumentIndex . ' must be an object, ' . $realType . ' given.');
- }
- break;
- case 'array':
- if (!is_array($mixedVar)) {
- if (is_object($mixedVar)) {
- if ($mixedVar instanceof ArrayAccess) {
- return;
- }
- }
- throw new TypeError('Argument ' . $argumentIndex . ' must be an array, ' . $realType . ' given.');
- }
- break;
- default:
- throw new SodiumException('Unknown type (' . $realType .') does not match expect type (' . $type . ')');
- }
- }
-
- /**
- * Evaluate whether or not two strings are equal (in constant-time)
- *
- * @param string $left
- * @param string $right
- * @return bool
- * @throws SodiumException
- * @throws TypeError
- */
- public static function hashEquals($left, $right)
- {
- /* Type checks: */
- if (!is_string($left)) {
- throw new TypeError('Argument 1 must be a string, ' . gettype($left) . ' given.');
- }
- if (!is_string($right)) {
- throw new TypeError('Argument 2 must be a string, ' . gettype($right) . ' given.');
- }
-
- if (is_callable('hash_equals')) {
- return hash_equals($left, $right);
- }
- $d = 0;
- /** @var int $len */
- $len = self::strlen($left);
- if ($len !== self::strlen($right)) {
- return false;
- }
- for ($i = 0; $i < $len; ++$i) {
- $d |= self::chrToInt($left[$i]) ^ self::chrToInt($right[$i]);
- }
-
- if ($d !== 0) {
- return false;
- }
- return $left === $right;
- }
-
- /**
- * Catch hash_update() failures and throw instead of silently proceding
- *
- * @param HashContext|resource &$hs
- * @param string $data
- * @return void
- * @throws SodiumException
- * @psalm-suppress PossiblyInvalidArgument
- */
- protected static function hash_update(&$hs, $data)
- {
- if (!hash_update($hs, $data)) {
- throw new SodiumException('hash_update() failed');
- }
- }
-
- /**
- * Convert a hexadecimal string into a binary string without cache-timing
- * leaks
- *
- * @internal You should not use this directly from another application
- *
- * @param string $hexString
- * @param bool $strictPadding
- * @return string (raw binary)
- * @throws RangeException
- * @throws TypeError
- */
- public static function hex2bin($hexString, $strictPadding = false)
- {
- /* Type checks: */
- if (!is_string($hexString)) {
- throw new TypeError('Argument 1 must be a string, ' . gettype($hexString) . ' given.');
- }
-
- /** @var int $hex_pos */
- $hex_pos = 0;
- /** @var string $bin */
- $bin = '';
- /** @var int $c_acc */
- $c_acc = 0;
- /** @var int $hex_len */
- $hex_len = self::strlen($hexString);
- /** @var int $state */
- $state = 0;
- if (($hex_len & 1) !== 0) {
- if ($strictPadding) {
- throw new RangeException(
- 'Expected an even number of hexadecimal characters'
- );
- } else {
- $hexString = '0' . $hexString;
- ++$hex_len;
- }
- }
-
- $chunk = unpack('C*', $hexString);
- while ($hex_pos < $hex_len) {
- ++$hex_pos;
- /** @var int $c */
- $c = $chunk[$hex_pos];
- /** @var int $c_num */
- $c_num = $c ^ 48;
- /** @var int $c_num0 */
- $c_num0 = ($c_num - 10) >> 8;
- /** @var int $c_alpha */
- $c_alpha = ($c & ~32) - 55;
- /** @var int $c_alpha0 */
- $c_alpha0 = (($c_alpha - 10) ^ ($c_alpha - 16)) >> 8;
- if (($c_num0 | $c_alpha0) === 0) {
- throw new RangeException(
- 'hex2bin() only expects hexadecimal characters'
- );
- }
- /** @var int $c_val */
- $c_val = ($c_num0 & $c_num) | ($c_alpha & $c_alpha0);
- if ($state === 0) {
- $c_acc = $c_val * 16;
- } else {
- $bin .= pack('C', $c_acc | $c_val);
- }
- $state ^= 1;
- }
- return $bin;
- }
-
- /**
- * Turn an array of integers into a string
- *
- * @internal You should not use this directly from another application
- *
- * @param array<int, int> $ints
- * @return string
- */
- public static function intArrayToString(array $ints)
- {
- /** @var array<int, int> $args */
- $args = $ints;
- foreach ($args as $i => $v) {
- $args[$i] = (int) ($v & 0xff);
- }
- array_unshift($args, str_repeat('C', count($ints)));
- return (string) (call_user_func_array('pack', $args));
- }
-
- /**
- * Cache-timing-safe variant of ord()
- *
- * @internal You should not use this directly from another application
- *
- * @param int $int
- * @return string
- * @throws TypeError
- */
- public static function intToChr($int)
- {
- return pack('C', $int);
- }
-
- /**
- * Load a 3 character substring into an integer
- *
- * @internal You should not use this directly from another application
- *
- * @param string $string
- * @return int
- * @throws RangeException
- * @throws TypeError
- */
- public static function load_3($string)
- {
- /* Type checks: */
- if (!is_string($string)) {
- throw new TypeError('Argument 1 must be a string, ' . gettype($string) . ' given.');
- }
-
- /* Input validation: */
- if (self::strlen($string) < 3) {
- throw new RangeException(
- 'String must be 3 bytes or more; ' . self::strlen($string) . ' given.'
- );
- }
- /** @var array<int, int> $unpacked */
- $unpacked = unpack('V', $string . "\0");
- return (int) ($unpacked[1] & 0xffffff);
- }
-
- /**
- * Load a 4 character substring into an integer
- *
- * @internal You should not use this directly from another application
- *
- * @param string $string
- * @return int
- * @throws RangeException
- * @throws TypeError
- */
- public static function load_4($string)
- {
- /* Type checks: */
- if (!is_string($string)) {
- throw new TypeError('Argument 1 must be a string, ' . gettype($string) . ' given.');
- }
-
- /* Input validation: */
- if (self::strlen($string) < 4) {
- throw new RangeException(
- 'String must be 4 bytes or more; ' . self::strlen($string) . ' given.'
- );
- }
- /** @var array<int, int> $unpacked */
- $unpacked = unpack('V', $string);
- return (int) ($unpacked[1] & 0xffffffff);
- }
-
- /**
- * Load a 8 character substring into an integer
- *
- * @internal You should not use this directly from another application
- *
- * @param string $string
- * @return int
- * @throws RangeException
- * @throws SodiumException
- * @throws TypeError
- */
- public static function load64_le($string)
- {
- /* Type checks: */
- if (!is_string($string)) {
- throw new TypeError('Argument 1 must be a string, ' . gettype($string) . ' given.');
- }
-
- /* Input validation: */
- if (self::strlen($string) < 4) {
- throw new RangeException(
- 'String must be 4 bytes or more; ' . self::strlen($string) . ' given.'
- );
- }
- if (PHP_VERSION_ID >= 50603 && PHP_INT_SIZE === 8) {
- /** @var array<int, int> $unpacked */
- $unpacked = unpack('P', $string);
- return (int) $unpacked[1];
- }
-
- /** @var int $result */
- $result = (self::chrToInt($string[0]) & 0xff);
- $result |= (self::chrToInt($string[1]) & 0xff) << 8;
- $result |= (self::chrToInt($string[2]) & 0xff) << 16;
- $result |= (self::chrToInt($string[3]) & 0xff) << 24;
- $result |= (self::chrToInt($string[4]) & 0xff) << 32;
- $result |= (self::chrToInt($string[5]) & 0xff) << 40;
- $result |= (self::chrToInt($string[6]) & 0xff) << 48;
- $result |= (self::chrToInt($string[7]) & 0xff) << 56;
- return (int) $result;
- }
-
- /**
- * @internal You should not use this directly from another application
- *
- * @param string $left
- * @param string $right
- * @return int
- * @throws SodiumException
- * @throws TypeError
- */
- public static function memcmp($left, $right)
- {
- if (self::hashEquals($left, $right)) {
- return 0;
- }
- return -1;
- }
-
- /**
- * Multiply two integers in constant-time
- *
- * Micro-architecture timing side-channels caused by how your CPU
- * implements multiplication are best prevented by never using the
- * multiplication operators and ensuring that our code always takes
- * the same number of operations to complete, regardless of the values
- * of $a and $b.
- *
- * @internal You should not use this directly from another application
- *
- * @param int $a
- * @param int $b
- * @param int $size Limits the number of operations (useful for small,
- * constant operands)
- * @return int
- */
- public static function mul($a, $b, $size = 0)
- {
- if (ParagonIE_Sodium_Compat::$fastMult) {
- return (int) ($a * $b);
- }
-
- static $defaultSize = null;
- /** @var int $defaultSize */
- if (!$defaultSize) {
- /** @var int $defaultSize */
- $defaultSize = (PHP_INT_SIZE << 3) - 1;
- }
- if ($size < 1) {
- /** @var int $size */
- $size = $defaultSize;
- }
- /** @var int $size */
-
- $c = 0;
-
- /**
- * Mask is either -1 or 0.
- *
- * -1 in binary looks like 0x1111 ... 1111
- * 0 in binary looks like 0x0000 ... 0000
- *
- * @var int
- */
- $mask = -(($b >> ((int) $defaultSize)) & 1);
-
- /**
- * Ensure $b is a positive integer, without creating
- * a branching side-channel
- *
- * @var int $b
- */
- $b = ($b & ~$mask) | ($mask & -$b);
-
- /**
- * Unless $size is provided:
- *
- * This loop always runs 32 times when PHP_INT_SIZE is 4.
- * This loop always runs 64 times when PHP_INT_SIZE is 8.
- */
- for ($i = $size; $i >= 0; --$i) {
- $c += (int) ($a & -($b & 1));
- $a <<= 1;
- $b >>= 1;
- }
-
- /**
- * If $b was negative, we then apply the same value to $c here.
- * It doesn't matter much if $a was negative; the $c += above would
- * have produced a negative integer to begin with. But a negative $b
- * makes $b >>= 1 never return 0, so we would end up with incorrect
- * results.
- *
- * The end result is what we'd expect from integer multiplication.
- */
- return (int) (($c & ~$mask) | ($mask & -$c));
- }
-
- /**
- * Convert any arbitrary numbers into two 32-bit integers that represent
- * a 64-bit integer.
- *
- * @internal You should not use this directly from another application
- *
- * @param int|float $num
- * @return array<int, int>
- */
- public static function numericTo64BitInteger($num)
- {
- $high = 0;
- /** @var int $low */
- $low = $num & 0xffffffff;
-
- if ((+(abs($num))) >= 1) {
- if ($num > 0) {
- /** @var int $high */
- $high = min((+(floor($num/4294967296))), 4294967295);
- } else {
- /** @var int $high */
- $high = ~~((+(ceil(($num - (+((~~($num)))))/4294967296))));
- }
- }
- return array((int) $high, (int) $low);
- }
-
- /**
- * Store a 24-bit integer into a string, treating it as big-endian.
- *
- * @internal You should not use this directly from another application
- *
- * @param int $int
- * @return string
- * @throws TypeError
- */
- public static function store_3($int)
- {
- /* Type checks: */
- if (!is_int($int)) {
- if (is_numeric($int)) {
- $int = (int) $int;
- } else {
- throw new TypeError('Argument 1 must be an integer, ' . gettype($int) . ' given.');
- }
- }
- /** @var string $packed */
- $packed = pack('N', $int);
- return self::substr($packed, 1, 3);
- }
-
- /**
- * Store a 32-bit integer into a string, treating it as little-endian.
- *
- * @internal You should not use this directly from another application
- *
- * @param int $int
- * @return string
- * @throws TypeError
- */
- public static function store32_le($int)
- {
- /* Type checks: */
- if (!is_int($int)) {
- if (is_numeric($int)) {
- $int = (int) $int;
- } else {
- throw new TypeError('Argument 1 must be an integer, ' . gettype($int) . ' given.');
- }
- }
-
- /** @var string $packed */
- $packed = pack('V', $int);
- return $packed;
- }
-
- /**
- * Store a 32-bit integer into a string, treating it as big-endian.
- *
- * @internal You should not use this directly from another application
- *
- * @param int $int
- * @return string
- * @throws TypeError
- */
- public static function store_4($int)
- {
- /* Type checks: */
- if (!is_int($int)) {
- if (is_numeric($int)) {
- $int = (int) $int;
- } else {
- throw new TypeError('Argument 1 must be an integer, ' . gettype($int) . ' given.');
- }
- }
-
- /** @var string $packed */
- $packed = pack('N', $int);
- return $packed;
- }
-
- /**
- * Stores a 64-bit integer as an string, treating it as little-endian.
- *
- * @internal You should not use this directly from another application
- *
- * @param int $int
- * @return string
- * @throws TypeError
- */
- public static function store64_le($int)
- {
- /* Type checks: */
- if (!is_int($int)) {
- if (is_numeric($int)) {
- $int = (int) $int;
- } else {
- throw new TypeError('Argument 1 must be an integer, ' . gettype($int) . ' given.');
- }
- }
-
- if (PHP_INT_SIZE === 8) {
- if (PHP_VERSION_ID >= 50603) {
- /** @var string $packed */
- $packed = pack('P', $int);
- return $packed;
- }
- return self::intToChr($int & 0xff) .
- self::intToChr(($int >> 8) & 0xff) .
- self::intToChr(($int >> 16) & 0xff) .
- self::intToChr(($int >> 24) & 0xff) .
- self::intToChr(($int >> 32) & 0xff) .
- self::intToChr(($int >> 40) & 0xff) .
- self::intToChr(($int >> 48) & 0xff) .
- self::intToChr(($int >> 56) & 0xff);
- }
- if ($int > PHP_INT_MAX) {
- list($hiB, $int) = self::numericTo64BitInteger($int);
- } else {
- $hiB = 0;
- }
- return
- self::intToChr(($int ) & 0xff) .
- self::intToChr(($int >> 8) & 0xff) .
- self::intToChr(($int >> 16) & 0xff) .
- self::intToChr(($int >> 24) & 0xff) .
- self::intToChr($hiB & 0xff) .
- self::intToChr(($hiB >> 8) & 0xff) .
- self::intToChr(($hiB >> 16) & 0xff) .
- self::intToChr(($hiB >> 24) & 0xff);
- }
-
- /**
- * Safe string length
- *
- * @internal You should not use this directly from another application
- *
- * @ref mbstring.func_overload
- *
- * @param string $str
- * @return int
- * @throws TypeError
- */
- public static function strlen($str)
- {
- /* Type checks: */
- if (!is_string($str)) {
- throw new TypeError('String expected');
- }
-
- return (int) (
- self::isMbStringOverride()
- ? mb_strlen($str, '8bit')
- : strlen($str)
- );
- }
-
- /**
- * Turn a string into an array of integers
- *
- * @internal You should not use this directly from another application
- *
- * @param string $string
- * @return array<int, int>
- * @throws TypeError
- */
- public static function stringToIntArray($string)
- {
- if (!is_string($string)) {
- throw new TypeError('String expected');
- }
- /**
- * @var array<int, int>
- */
- $values = array_values(
- unpack('C*', $string)
- );
- return $values;
- }
-
- /**
- * Safe substring
- *
- * @internal You should not use this directly from another application
- *
- * @ref mbstring.func_overload
- *
- * @param string $str
- * @param int $start
- * @param int $length
- * @return string
- * @throws TypeError
- */
- public static function substr($str, $start = 0, $length = null)
- {
- /* Type checks: */
- if (!is_string($str)) {
- throw new TypeError('String expected');
- }
-
- if ($length === 0) {
- return '';
- }
-
- if (self::isMbStringOverride()) {
- if (PHP_VERSION_ID < 50400 && $length === null) {
- $length = self::strlen($str);
- }
- $sub = (string) mb_substr($str, $start, $length, '8bit');
- } elseif ($length === null) {
- $sub = (string) substr($str, $start);
- } else {
- $sub = (string) substr($str, $start, $length);
- }
- if ($sub !== '') {
- return $sub;
- }
- return '';
- }
-
- /**
- * Compare a 16-character byte string in constant time.
- *
- * @internal You should not use this directly from another application
- *
- * @param string $a
- * @param string $b
- * @return bool
- * @throws SodiumException
- * @throws TypeError
- */
- public static function verify_16($a, $b)
- {
- /* Type checks: */
- if (!is_string($a)) {
- throw new TypeError('String expected');
- }
- if (!is_string($b)) {
- throw new TypeError('String expected');
- }
- return self::hashEquals(
- self::substr($a, 0, 16),
- self::substr($b, 0, 16)
- );
- }
-
- /**
- * Compare a 32-character byte string in constant time.
- *
- * @internal You should not use this directly from another application
- *
- * @param string $a
- * @param string $b
- * @return bool
- * @throws SodiumException
- * @throws TypeError
- */
- public static function verify_32($a, $b)
- {
- /* Type checks: */
- if (!is_string($a)) {
- throw new TypeError('String expected');
- }
- if (!is_string($b)) {
- throw new TypeError('String expected');
- }
- return self::hashEquals(
- self::substr($a, 0, 32),
- self::substr($b, 0, 32)
- );
- }
-
- /**
- * Calculate $a ^ $b for two strings.
- *
- * @internal You should not use this directly from another application
- *
- * @param string $a
- * @param string $b
- * @return string
- * @throws TypeError
- */
- public static function xorStrings($a, $b)
- {
- /* Type checks: */
- if (!is_string($a)) {
- throw new TypeError('Argument 1 must be a string');
- }
- if (!is_string($b)) {
- throw new TypeError('Argument 2 must be a string');
- }
-
- return (string) ($a ^ $b);
- }
-
- /**
- * Returns whether or not mbstring.func_overload is in effect.
- *
- * @internal You should not use this directly from another application
- *
- * Note: MB_OVERLOAD_STRING === 2, but we don't reference the constant
- * (for nuisance-free PHP 8 support)
- *
- * @return bool
- */
- protected static function isMbStringOverride()
- {
- static $mbstring = null;
-
- if ($mbstring === null) {
- $mbstring = extension_loaded('mbstring')
- && defined('MB_OVERLOAD_STRING')
- &&
- ((int) (ini_get('mbstring.func_overload')) & 2);
- // MB_OVERLOAD_STRING === 2
- }
- /** @var bool $mbstring */
-
- return $mbstring;
- }
-}
+<?php
+
+if (class_exists('ParagonIE_Sodium_Core_Util', false)) {
+ return;
+}
+
+/**
+ * Class ParagonIE_Sodium_Core_Util
+ */
+abstract class ParagonIE_Sodium_Core_Util
+{
+ /**
+ * @param int $integer
+ * @param int $size (16, 32, 64)
+ * @return int
+ */
+ public static function abs($integer, $size = 0)
+ {
+ /** @var int $realSize */
+ $realSize = (PHP_INT_SIZE << 3) - 1;
+ if ($size) {
+ --$size;
+ } else {
+ /** @var int $size */
+ $size = $realSize;
+ }
+
+ $negative = -(($integer >> $size) & 1);
+ return (int) (
+ ($integer ^ $negative)
+ +
+ (($negative >> $realSize) & 1)
+ );
+ }
+
+ /**
+ * Convert a binary string into a hexadecimal string without cache-timing
+ * leaks
+ *
+ * @internal You should not use this directly from another application
+ *
+ * @param string $binaryString (raw binary)
+ * @return string
+ * @throws TypeError
+ */
+ public static function bin2hex($binaryString)
+ {
+ /* Type checks: */
+ if (!is_string($binaryString)) {
+ throw new TypeError('Argument 1 must be a string, ' . gettype($binaryString) . ' given.');
+ }
+
+ $hex = '';
+ $len = self::strlen($binaryString);
+ for ($i = 0; $i < $len; ++$i) {
+ /** @var array<int, int> $chunk */
+ $chunk = unpack('C', $binaryString[$i]);
+ /** @var int $c */
+ $c = $chunk[1] & 0xf;
+ /** @var int $b */
+ $b = $chunk[1] >> 4;
+ $hex .= pack(
+ 'CC',
+ (87 + $b + ((($b - 10) >> 8) & ~38)),
+ (87 + $c + ((($c - 10) >> 8) & ~38))
+ );
+ }
+ return $hex;
+ }
+
+ /**
+ * Convert a binary string into a hexadecimal string without cache-timing
+ * leaks, returning uppercase letters (as per RFC 4648)
+ *
+ * @internal You should not use this directly from another application
+ *
+ * @param string $bin_string (raw binary)
+ * @return string
+ * @throws TypeError
+ */
+ public static function bin2hexUpper($bin_string)
+ {
+ $hex = '';
+ $len = self::strlen($bin_string);
+ for ($i = 0; $i < $len; ++$i) {
+ /** @var array<int, int> $chunk */
+ $chunk = unpack('C', $bin_string[$i]);
+ /**
+ * Lower 16 bits
+ *
+ * @var int $c
+ */
+ $c = $chunk[1] & 0xf;
+
+ /**
+ * Upper 16 bits
+ * @var int $b
+ */
+ $b = $chunk[1] >> 4;
+
+ /**
+ * Use pack() and binary operators to turn the two integers
+ * into hexadecimal characters. We don't use chr() here, because
+ * it uses a lookup table internally and we want to avoid
+ * cache-timing side-channels.
+ */
+ $hex .= pack(
+ 'CC',
+ (55 + $b + ((($b - 10) >> 8) & ~6)),
+ (55 + $c + ((($c - 10) >> 8) & ~6))
+ );
+ }
+ return $hex;
+ }
+
+ /**
+ * Cache-timing-safe variant of ord()
+ *
+ * @internal You should not use this directly from another application
+ *
+ * @param string $chr
+ * @return int
+ * @throws SodiumException
+ * @throws TypeError
+ */
+ public static function chrToInt($chr)
+ {
+ /* Type checks: */
+ if (!is_string($chr)) {
+ throw new TypeError('Argument 1 must be a string, ' . gettype($chr) . ' given.');
+ }
+ if (self::strlen($chr) !== 1) {
+ throw new SodiumException('chrToInt() expects a string that is exactly 1 character long');
+ }
+ /** @var array<int, int> $chunk */
+ $chunk = unpack('C', $chr);
+ return (int) ($chunk[1]);
+ }
+
+ /**
+ * Compares two strings.
+ *
+ * @internal You should not use this directly from another application
+ *
+ * @param string $left
+ * @param string $right
+ * @param int $len
+ * @return int
+ * @throws SodiumException
+ * @throws TypeError
+ */
+ public static function compare($left, $right, $len = null)
+ {
+ $leftLen = self::strlen($left);
+ $rightLen = self::strlen($right);
+ if ($len === null) {
+ $len = max($leftLen, $rightLen);
+ $left = str_pad($left, $len, "\x00", STR_PAD_RIGHT);
+ $right = str_pad($right, $len, "\x00", STR_PAD_RIGHT);
+ }
+
+ $gt = 0;
+ $eq = 1;
+ $i = $len;
+ while ($i !== 0) {
+ --$i;
+ $gt |= ((self::chrToInt($right[$i]) - self::chrToInt($left[$i])) >> 8) & $eq;
+ $eq &= ((self::chrToInt($right[$i]) ^ self::chrToInt($left[$i])) - 1) >> 8;
+ }
+ return ($gt + $gt + $eq) - 1;
+ }
+
+ /**
+ * If a variable does not match a given type, throw a TypeError.
+ *
+ * @param mixed $mixedVar
+ * @param string $type
+ * @param int $argumentIndex
+ * @throws TypeError
+ * @throws SodiumException
+ * @return void
+ */
+ public static function declareScalarType(&$mixedVar = null, $type = 'void', $argumentIndex = 0)
+ {
+ if (func_num_args() === 0) {
+ /* Tautology, by default */
+ return;
+ }
+ if (func_num_args() === 1) {
+ throw new TypeError('Declared void, but passed a variable');
+ }
+ $realType = strtolower(gettype($mixedVar));
+ $type = strtolower($type);
+ switch ($type) {
+ case 'null':
+ if ($mixedVar !== null) {
+ throw new TypeError('Argument ' . $argumentIndex . ' must be null, ' . $realType . ' given.');
+ }
+ break;
+ case 'integer':
+ case 'int':
+ $allow = array('int', 'integer');
+ if (!in_array($type, $allow)) {
+ throw new TypeError('Argument ' . $argumentIndex . ' must be an integer, ' . $realType . ' given.');
+ }
+ $mixedVar = (int) $mixedVar;
+ break;
+ case 'boolean':
+ case 'bool':
+ $allow = array('bool', 'boolean');
+ if (!in_array($type, $allow)) {
+ throw new TypeError('Argument ' . $argumentIndex . ' must be a boolean, ' . $realType . ' given.');
+ }
+ $mixedVar = (bool) $mixedVar;
+ break;
+ case 'string':
+ if (!is_string($mixedVar)) {
+ throw new TypeError('Argument ' . $argumentIndex . ' must be a string, ' . $realType . ' given.');
+ }
+ $mixedVar = (string) $mixedVar;
+ break;
+ case 'decimal':
+ case 'double':
+ case 'float':
+ $allow = array('decimal', 'double', 'float');
+ if (!in_array($type, $allow)) {
+ throw new TypeError('Argument ' . $argumentIndex . ' must be a float, ' . $realType . ' given.');
+ }
+ $mixedVar = (float) $mixedVar;
+ break;
+ case 'object':
+ if (!is_object($mixedVar)) {
+ throw new TypeError('Argument ' . $argumentIndex . ' must be an object, ' . $realType . ' given.');
+ }
+ break;
+ case 'array':
+ if (!is_array($mixedVar)) {
+ if (is_object($mixedVar)) {
+ if ($mixedVar instanceof ArrayAccess) {
+ return;
+ }
+ }
+ throw new TypeError('Argument ' . $argumentIndex . ' must be an array, ' . $realType . ' given.');
+ }
+ break;
+ default:
+ throw new SodiumException('Unknown type (' . $realType .') does not match expect type (' . $type . ')');
+ }
+ }
+
+ /**
+ * Evaluate whether or not two strings are equal (in constant-time)
+ *
+ * @param string $left
+ * @param string $right
+ * @return bool
+ * @throws SodiumException
+ * @throws TypeError
+ */
+ public static function hashEquals($left, $right)
+ {
+ /* Type checks: */
+ if (!is_string($left)) {
+ throw new TypeError('Argument 1 must be a string, ' . gettype($left) . ' given.');
+ }
+ if (!is_string($right)) {
+ throw new TypeError('Argument 2 must be a string, ' . gettype($right) . ' given.');
+ }
+
+ if (is_callable('hash_equals')) {
+ return hash_equals($left, $right);
+ }
+ $d = 0;
+ /** @var int $len */
+ $len = self::strlen($left);
+ if ($len !== self::strlen($right)) {
+ return false;
+ }
+ for ($i = 0; $i < $len; ++$i) {
+ $d |= self::chrToInt($left[$i]) ^ self::chrToInt($right[$i]);
+ }
+
+ if ($d !== 0) {
+ return false;
+ }
+ return $left === $right;
+ }
+
+ /**
+ * Catch hash_update() failures and throw instead of silently proceeding
+ *
+ * @param HashContext|resource &$hs
+ * @param string $data
+ * @return void
+ * @throws SodiumException
+ * @psalm-suppress PossiblyInvalidArgument
+ */
+ protected static function hash_update(&$hs, $data)
+ {
+ if (!hash_update($hs, $data)) {
+ throw new SodiumException('hash_update() failed');
+ }
+ }
+
+ /**
+ * Convert a hexadecimal string into a binary string without cache-timing
+ * leaks
+ *
+ * @internal You should not use this directly from another application
+ *
+ * @param string $hexString
+ * @param string $ignore
+ * @param bool $strictPadding
+ * @return string (raw binary)
+ * @throws RangeException
+ * @throws TypeError
+ */
+ public static function hex2bin($hexString, $ignore = '', $strictPadding = false)
+ {
+ /* Type checks: */
+ if (!is_string($hexString)) {
+ throw new TypeError('Argument 1 must be a string, ' . gettype($hexString) . ' given.');
+ }
+ if (!is_string($ignore)) {
+ throw new TypeError('Argument 2 must be a string, ' . gettype($hexString) . ' given.');
+ }
+
+ $hex_pos = 0;
+ $bin = '';
+ $c_acc = 0;
+ $hex_len = self::strlen($hexString);
+ $state = 0;
+ if (($hex_len & 1) !== 0) {
+ if ($strictPadding) {
+ throw new RangeException(
+ 'Expected an even number of hexadecimal characters'
+ );
+ } else {
+ $hexString = '0' . $hexString;
+ ++$hex_len;
+ }
+ }
+
+ $chunk = unpack('C*', $hexString);
+ while ($hex_pos < $hex_len) {
+ ++$hex_pos;
+ /** @var int $c */
+ $c = $chunk[$hex_pos];
+ $c_num = $c ^ 48;
+ $c_num0 = ($c_num - 10) >> 8;
+ $c_alpha = ($c & ~32) - 55;
+ $c_alpha0 = (($c_alpha - 10) ^ ($c_alpha - 16)) >> 8;
+ if (($c_num0 | $c_alpha0) === 0) {
+ if ($ignore && $state === 0 && strpos($ignore, self::intToChr($c)) !== false) {
+ continue;
+ }
+ throw new RangeException(
+ 'hex2bin() only expects hexadecimal characters'
+ );
+ }
+ $c_val = ($c_num0 & $c_num) | ($c_alpha & $c_alpha0);
+ if ($state === 0) {
+ $c_acc = $c_val * 16;
+ } else {
+ $bin .= pack('C', $c_acc | $c_val);
+ }
+ $state ^= 1;
+ }
+ return $bin;
+ }
+
+ /**
+ * Turn an array of integers into a string
+ *
+ * @internal You should not use this directly from another application
+ *
+ * @param array<int, int> $ints
+ * @return string
+ */
+ public static function intArrayToString(array $ints)
+ {
+ $args = $ints;
+ foreach ($args as $i => $v) {
+ $args[$i] = (int) ($v & 0xff);
+ }
+ array_unshift($args, str_repeat('C', count($ints)));
+ return (string) (call_user_func_array('pack', $args));
+ }
+
+ /**
+ * Cache-timing-safe variant of ord()
+ *
+ * @internal You should not use this directly from another application
+ *
+ * @param int $int
+ * @return string
+ * @throws TypeError
+ */
+ public static function intToChr($int)
+ {
+ return pack('C', $int);
+ }
+
+ /**
+ * Load a 3 character substring into an integer
+ *
+ * @internal You should not use this directly from another application
+ *
+ * @param string $string
+ * @return int
+ * @throws RangeException
+ * @throws TypeError
+ */
+ public static function load_3($string)
+ {
+ /* Type checks: */
+ if (!is_string($string)) {
+ throw new TypeError('Argument 1 must be a string, ' . gettype($string) . ' given.');
+ }
+
+ /* Input validation: */
+ if (self::strlen($string) < 3) {
+ throw new RangeException(
+ 'String must be 3 bytes or more; ' . self::strlen($string) . ' given.'
+ );
+ }
+ /** @var array<int, int> $unpacked */
+ $unpacked = unpack('V', $string . "\0");
+ return (int) ($unpacked[1] & 0xffffff);
+ }
+
+ /**
+ * Load a 4 character substring into an integer
+ *
+ * @internal You should not use this directly from another application
+ *
+ * @param string $string
+ * @return int
+ * @throws RangeException
+ * @throws TypeError
+ */
+ public static function load_4($string)
+ {
+ /* Type checks: */
+ if (!is_string($string)) {
+ throw new TypeError('Argument 1 must be a string, ' . gettype($string) . ' given.');
+ }
+
+ /* Input validation: */
+ if (self::strlen($string) < 4) {
+ throw new RangeException(
+ 'String must be 4 bytes or more; ' . self::strlen($string) . ' given.'
+ );
+ }
+ /** @var array<int, int> $unpacked */
+ $unpacked = unpack('V', $string);
+ return (int) $unpacked[1];
+ }
+
+ /**
+ * Load a 8 character substring into an integer
+ *
+ * @internal You should not use this directly from another application
+ *
+ * @param string $string
+ * @return int
+ * @throws RangeException
+ * @throws SodiumException
+ * @throws TypeError
+ */
+ public static function load64_le($string)
+ {
+ /* Type checks: */
+ if (!is_string($string)) {
+ throw new TypeError('Argument 1 must be a string, ' . gettype($string) . ' given.');
+ }
+
+ /* Input validation: */
+ if (self::strlen($string) < 4) {
+ throw new RangeException(
+ 'String must be 4 bytes or more; ' . self::strlen($string) . ' given.'
+ );
+ }
+ if (PHP_VERSION_ID >= 50603 && PHP_INT_SIZE === 8) {
+ /** @var array<int, int> $unpacked */
+ $unpacked = unpack('P', $string);
+ return (int) $unpacked[1];
+ }
+
+ /** @var int $result */
+ $result = (self::chrToInt($string[0]) & 0xff);
+ $result |= (self::chrToInt($string[1]) & 0xff) << 8;
+ $result |= (self::chrToInt($string[2]) & 0xff) << 16;
+ $result |= (self::chrToInt($string[3]) & 0xff) << 24;
+ $result |= (self::chrToInt($string[4]) & 0xff) << 32;
+ $result |= (self::chrToInt($string[5]) & 0xff) << 40;
+ $result |= (self::chrToInt($string[6]) & 0xff) << 48;
+ $result |= (self::chrToInt($string[7]) & 0xff) << 56;
+ return (int) $result;
+ }
+
+ /**
+ * @internal You should not use this directly from another application
+ *
+ * @param string $left
+ * @param string $right
+ * @return int
+ * @throws SodiumException
+ * @throws TypeError
+ */
+ public static function memcmp($left, $right)
+ {
+ if (self::hashEquals($left, $right)) {
+ return 0;
+ }
+ return -1;
+ }
+
+ /**
+ * Multiply two integers in constant-time
+ *
+ * Micro-architecture timing side-channels caused by how your CPU
+ * implements multiplication are best prevented by never using the
+ * multiplication operators and ensuring that our code always takes
+ * the same number of operations to complete, regardless of the values
+ * of $a and $b.
+ *
+ * @internal You should not use this directly from another application
+ *
+ * @param int $a
+ * @param int $b
+ * @param int $size Limits the number of operations (useful for small,
+ * constant operands)
+ * @return int
+ */
+ public static function mul($a, $b, $size = 0)
+ {
+ if (ParagonIE_Sodium_Compat::$fastMult) {
+ return (int) ($a * $b);
+ }
+
+ static $defaultSize = null;
+ /** @var int $defaultSize */
+ if (!$defaultSize) {
+ /** @var int $defaultSize */
+ $defaultSize = (PHP_INT_SIZE << 3) - 1;
+ }
+ if ($size < 1) {
+ /** @var int $size */
+ $size = $defaultSize;
+ }
+ /** @var int $size */
+
+ $c = 0;
+
+ /**
+ * Mask is either -1 or 0.
+ *
+ * -1 in binary looks like 0x1111 ... 1111
+ * 0 in binary looks like 0x0000 ... 0000
+ *
+ * @var int
+ */
+ $mask = -(($b >> ((int) $defaultSize)) & 1);
+
+ /**
+ * Ensure $b is a positive integer, without creating
+ * a branching side-channel
+ *
+ * @var int $b
+ */
+ $b = ($b & ~$mask) | ($mask & -$b);
+
+ /**
+ * Unless $size is provided:
+ *
+ * This loop always runs 32 times when PHP_INT_SIZE is 4.
+ * This loop always runs 64 times when PHP_INT_SIZE is 8.
+ */
+ for ($i = $size; $i >= 0; --$i) {
+ $c += (int) ($a & -($b & 1));
+ $a <<= 1;
+ $b >>= 1;
+ }
+ $c = (int) @($c & -1);
+
+ /**
+ * If $b was negative, we then apply the same value to $c here.
+ * It doesn't matter much if $a was negative; the $c += above would
+ * have produced a negative integer to begin with. But a negative $b
+ * makes $b >>= 1 never return 0, so we would end up with incorrect
+ * results.
+ *
+ * The end result is what we'd expect from integer multiplication.
+ */
+ return (int) (($c & ~$mask) | ($mask & -$c));
+ }
+
+ /**
+ * Convert any arbitrary numbers into two 32-bit integers that represent
+ * a 64-bit integer.
+ *
+ * @internal You should not use this directly from another application
+ *
+ * @param int|float $num
+ * @return array<int, int>
+ */
+ public static function numericTo64BitInteger($num)
+ {
+ $high = 0;
+ /** @var int $low */
+ if (PHP_INT_SIZE === 4) {
+ $low = (int) $num;
+ } else {
+ $low = $num & 0xffffffff;
+ }
+
+ if ((+(abs($num))) >= 1) {
+ if ($num > 0) {
+ /** @var int $high */
+ $high = min((+(floor($num/4294967296))), 4294967295);
+ } else {
+ /** @var int $high */
+ $high = ~~((+(ceil(($num - (+((~~($num)))))/4294967296))));
+ }
+ }
+ return array((int) $high, (int) $low);
+ }
+
+ /**
+ * Store a 24-bit integer into a string, treating it as big-endian.
+ *
+ * @internal You should not use this directly from another application
+ *
+ * @param int $int
+ * @return string
+ * @throws TypeError
+ */
+ public static function store_3($int)
+ {
+ /* Type checks: */
+ if (!is_int($int)) {
+ if (is_numeric($int)) {
+ $int = (int) $int;
+ } else {
+ throw new TypeError('Argument 1 must be an integer, ' . gettype($int) . ' given.');
+ }
+ }
+ /** @var string $packed */
+ $packed = pack('N', $int);
+ return self::substr($packed, 1, 3);
+ }
+
+ /**
+ * Store a 32-bit integer into a string, treating it as little-endian.
+ *
+ * @internal You should not use this directly from another application
+ *
+ * @param int $int
+ * @return string
+ * @throws TypeError
+ */
+ public static function store32_le($int)
+ {
+ /* Type checks: */
+ if (!is_int($int)) {
+ if (is_numeric($int)) {
+ $int = (int) $int;
+ } else {
+ throw new TypeError('Argument 1 must be an integer, ' . gettype($int) . ' given.');
+ }
+ }
+
+ /** @var string $packed */
+ $packed = pack('V', $int);
+ return $packed;
+ }
+
+ /**
+ * Store a 32-bit integer into a string, treating it as big-endian.
+ *
+ * @internal You should not use this directly from another application
+ *
+ * @param int $int
+ * @return string
+ * @throws TypeError
+ */
+ public static function store_4($int)
+ {
+ /* Type checks: */
+ if (!is_int($int)) {
+ if (is_numeric($int)) {
+ $int = (int) $int;
+ } else {
+ throw new TypeError('Argument 1 must be an integer, ' . gettype($int) . ' given.');
+ }
+ }
+
+ /** @var string $packed */
+ $packed = pack('N', $int);
+ return $packed;
+ }
+
+ /**
+ * Stores a 64-bit integer as an string, treating it as little-endian.
+ *
+ * @internal You should not use this directly from another application
+ *
+ * @param int $int
+ * @return string
+ * @throws TypeError
+ */
+ public static function store64_le($int)
+ {
+ /* Type checks: */
+ if (!is_int($int)) {
+ if (is_numeric($int)) {
+ $int = (int) $int;
+ } else {
+ throw new TypeError('Argument 1 must be an integer, ' . gettype($int) . ' given.');
+ }
+ }
+
+ if (PHP_INT_SIZE === 8) {
+ if (PHP_VERSION_ID >= 50603) {
+ /** @var string $packed */
+ $packed = pack('P', $int);
+ return $packed;
+ }
+ return self::intToChr($int & 0xff) .
+ self::intToChr(($int >> 8) & 0xff) .
+ self::intToChr(($int >> 16) & 0xff) .
+ self::intToChr(($int >> 24) & 0xff) .
+ self::intToChr(($int >> 32) & 0xff) .
+ self::intToChr(($int >> 40) & 0xff) .
+ self::intToChr(($int >> 48) & 0xff) .
+ self::intToChr(($int >> 56) & 0xff);
+ }
+ if ($int > PHP_INT_MAX) {
+ list($hiB, $int) = self::numericTo64BitInteger($int);
+ } else {
+ $hiB = 0;
+ }
+ return
+ self::intToChr(($int ) & 0xff) .
+ self::intToChr(($int >> 8) & 0xff) .
+ self::intToChr(($int >> 16) & 0xff) .
+ self::intToChr(($int >> 24) & 0xff) .
+ self::intToChr($hiB & 0xff) .
+ self::intToChr(($hiB >> 8) & 0xff) .
+ self::intToChr(($hiB >> 16) & 0xff) .
+ self::intToChr(($hiB >> 24) & 0xff);
+ }
+
+ /**
+ * Safe string length
+ *
+ * @internal You should not use this directly from another application
+ *
+ * @ref mbstring.func_overload
+ *
+ * @param string $str
+ * @return int
+ * @throws TypeError
+ */
+ public static function strlen($str)
+ {
+ /* Type checks: */
+ if (!is_string($str)) {
+ throw new TypeError('String expected');
+ }
+
+ return (int) (
+ self::isMbStringOverride()
+ ? mb_strlen($str, '8bit')
+ : strlen($str)
+ );
+ }
+
+ /**
+ * Turn a string into an array of integers
+ *
+ * @internal You should not use this directly from another application
+ *
+ * @param string $string
+ * @return array<int, int>
+ * @throws TypeError
+ */
+ public static function stringToIntArray($string)
+ {
+ if (!is_string($string)) {
+ throw new TypeError('String expected');
+ }
+ /**
+ * @var array<int, int>
+ */
+ $values = array_values(
+ unpack('C*', $string)
+ );
+ return $values;
+ }
+
+ /**
+ * Safe substring
+ *
+ * @internal You should not use this directly from another application
+ *
+ * @ref mbstring.func_overload
+ *
+ * @param string $str
+ * @param int $start
+ * @param int $length
+ * @return string
+ * @throws TypeError
+ */
+ public static function substr($str, $start = 0, $length = null)
+ {
+ /* Type checks: */
+ if (!is_string($str)) {
+ throw new TypeError('String expected');
+ }
+
+ if ($length === 0) {
+ return '';
+ }
+
+ if (self::isMbStringOverride()) {
+ if (PHP_VERSION_ID < 50400 && $length === null) {
+ $length = self::strlen($str);
+ }
+ $sub = (string) mb_substr($str, $start, $length, '8bit');
+ } elseif ($length === null) {
+ $sub = (string) substr($str, $start);
+ } else {
+ $sub = (string) substr($str, $start, $length);
+ }
+ if ($sub !== '') {
+ return $sub;
+ }
+ return '';
+ }
+
+ /**
+ * Compare a 16-character byte string in constant time.
+ *
+ * @internal You should not use this directly from another application
+ *
+ * @param string $a
+ * @param string $b
+ * @return bool
+ * @throws SodiumException
+ * @throws TypeError
+ */
+ public static function verify_16($a, $b)
+ {
+ /* Type checks: */
+ if (!is_string($a)) {
+ throw new TypeError('String expected');
+ }
+ if (!is_string($b)) {
+ throw new TypeError('String expected');
+ }
+ return self::hashEquals(
+ self::substr($a, 0, 16),
+ self::substr($b, 0, 16)
+ );
+ }
+
+ /**
+ * Compare a 32-character byte string in constant time.
+ *
+ * @internal You should not use this directly from another application
+ *
+ * @param string $a
+ * @param string $b
+ * @return bool
+ * @throws SodiumException
+ * @throws TypeError
+ */
+ public static function verify_32($a, $b)
+ {
+ /* Type checks: */
+ if (!is_string($a)) {
+ throw new TypeError('String expected');
+ }
+ if (!is_string($b)) {
+ throw new TypeError('String expected');
+ }
+ return self::hashEquals(
+ self::substr($a, 0, 32),
+ self::substr($b, 0, 32)
+ );
+ }
+
+ /**
+ * Calculate $a ^ $b for two strings.
+ *
+ * @internal You should not use this directly from another application
+ *
+ * @param string $a
+ * @param string $b
+ * @return string
+ * @throws TypeError
+ */
+ public static function xorStrings($a, $b)
+ {
+ /* Type checks: */
+ if (!is_string($a)) {
+ throw new TypeError('Argument 1 must be a string');
+ }
+ if (!is_string($b)) {
+ throw new TypeError('Argument 2 must be a string');
+ }
+
+ return (string) ($a ^ $b);
+ }
+
+ /**
+ * Returns whether or not mbstring.func_overload is in effect.
+ *
+ * @internal You should not use this directly from another application
+ *
+ * Note: MB_OVERLOAD_STRING === 2, but we don't reference the constant
+ * (for nuisance-free PHP 8 support)
+ *
+ * @return bool
+ */
+ protected static function isMbStringOverride()
+ {
+ static $mbstring = null;
+
+ if ($mbstring === null) {
+ if (!defined('MB_OVERLOAD_STRING')) {
+ $mbstring = false;
+ return $mbstring;
+ }
+ $mbstring = extension_loaded('mbstring')
+ && defined('MB_OVERLOAD_STRING')
+ &&
+ ((int) (ini_get('mbstring.func_overload')) & 2);
+ // MB_OVERLOAD_STRING === 2
+ }
+ /** @var bool $mbstring */
+
+ return $mbstring;
+ }
+}