if (class_exists('ParagonIE_Sodium_Core_Util', false)) {
* Class ParagonIE_Sodium_Core_Util
abstract class ParagonIE_Sodium_Core_Util
const U32_MAX = 0xFFFFFFFF;
* @param int $size (16, 32, 64)
public static function abs($integer, $size = 0)
/** @var int $realSize */
$realSize = (PHP_INT_SIZE << 3) - 1;
$negative = -(($integer >> $size) & 1);
(($negative >> $realSize) & 1)
* @throws SodiumException
public static function andStrings($a, $b)
throw new TypeError('Argument 1 must be a string');
throw new TypeError('Argument 2 must be a string');
if (self::strlen($b) !== $len) {
throw new SodiumException('Both strings must be of equal length to combine with bitwise AND');
* Convert a binary string into a hexadecimal string without cache-timing
* @internal You should not use this directly from another application
* @param string $binaryString (raw binary)
public static function bin2hex($binaryString)
if (!is_string($binaryString)) {
throw new TypeError('Argument 1 must be a string, ' . gettype($binaryString) . ' given.');
$len = self::strlen($binaryString);
for ($i = 0; $i < $len; ++$i) {
/** @var array<int, int> $chunk */
$chunk = unpack('C', $binaryString[$i]);
(87 + $b + ((($b - 10) >> 8) & ~38)),
(87 + $c + ((($c - 10) >> 8) & ~38))
* 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)
public static function bin2hexUpper($bin_string)
$len = self::strlen($bin_string);
for ($i = 0; $i < $len; ++$i) {
/** @var array<int, int> $chunk */
$chunk = unpack('C', $bin_string[$i]);
* 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.
(55 + $b + ((($b - 10) >> 8) & ~6)),
(55 + $c + ((($c - 10) >> 8) & ~6))
* Cache-timing-safe variant of ord()
* @internal You should not use this directly from another application
* @throws SodiumException
public static function chrToInt($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]);
* @internal You should not use this directly from another application
* @throws SodiumException
public static function compare($left, $right, $len = null)
$leftLen = self::strlen($left);
$rightLen = self::strlen($right);
$len = max($leftLen, $rightLen);
$left = str_pad($left, $len, "\x00", STR_PAD_RIGHT);
$right = str_pad($right, $len, "\x00", STR_PAD_RIGHT);
$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 int $argumentIndex
* @throws SodiumException
public static function declareScalarType(&$mixedVar = null, $type = 'void', $argumentIndex = 0)
if (func_num_args() === 0) {
/* Tautology, by default */
if (func_num_args() === 1) {
throw new TypeError('Declared void, but passed a variable');
$realType = strtolower(gettype($mixedVar));
$type = strtolower($type);
if ($mixedVar !== null) {
throw new TypeError('Argument ' . $argumentIndex . ' must be null, ' . $realType . ' given.');
$allow = array('int', 'integer');
if (!in_array($type, $allow)) {
throw new TypeError('Argument ' . $argumentIndex . ' must be an integer, ' . $realType . ' given.');
$mixedVar = (int) $mixedVar;
$allow = array('bool', 'boolean');
if (!in_array($type, $allow)) {
throw new TypeError('Argument ' . $argumentIndex . ' must be a boolean, ' . $realType . ' given.');
$mixedVar = (bool) $mixedVar;
if (!is_string($mixedVar)) {
throw new TypeError('Argument ' . $argumentIndex . ' must be a string, ' . $realType . ' given.');
$mixedVar = (string) $mixedVar;
$allow = array('decimal', 'double', 'float');
if (!in_array($type, $allow)) {
throw new TypeError('Argument ' . $argumentIndex . ' must be a float, ' . $realType . ' given.');
$mixedVar = (float) $mixedVar;
if (!is_object($mixedVar)) {
throw new TypeError('Argument ' . $argumentIndex . ' must be an object, ' . $realType . ' given.');
if (!is_array($mixedVar)) {
if (is_object($mixedVar)) {
if ($mixedVar instanceof ArrayAccess) {
throw new TypeError('Argument ' . $argumentIndex . ' must be an array, ' . $realType . ' given.');
throw new SodiumException('Unknown type (' . $realType .') does not match expect type (' . $type . ')');
* Evaluate whether or not two strings are equal (in constant-time)
* @throws SodiumException
public static function hashEquals($left, $right)
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);
$len = self::strlen($left);
if ($len !== self::strlen($right)) {
for ($i = 0; $i < $len; ++$i) {
$d |= self::chrToInt($left[$i]) ^ self::chrToInt($right[$i]);
* Catch hash_update() failures and throw instead of silently proceeding
* @param HashContext|resource &$hs
* @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
* @internal You should not use this directly from another application
* @param string $hexString
* @param bool $strictPadding
* @return string (raw binary)
public static function hex2bin($hexString, $ignore = '', $strictPadding = false)
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_len = self::strlen($hexString);
if (($hex_len & 1) !== 0) {
throw new RangeException(
'Expected an even number of hexadecimal characters'
$hexString = '0' . $hexString;
$chunk = unpack('C*', $hexString);
while ($hex_pos < $hex_len) {
$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) {
throw new RangeException(
'hex2bin() only expects hexadecimal characters'
$c_val = ($c_num0 & $c_num) | ($c_alpha & $c_alpha0);
$bin .= pack('C', $c_acc | $c_val);
* Turn an array of integers into a string
* @internal You should not use this directly from another application
* @param array<int, int> $ints
public static function intArrayToString(array $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
public static function intToChr($int)
* Load a 3 character substring into an integer
* @internal You should not use this directly from another application
public static function load_3($string)
if (!is_string($string)) {
throw new TypeError('Argument 1 must be a string, ' . gettype($string) . ' given.');
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
public static function load_4($string)
if (!is_string($string)) {
throw new TypeError('Argument 1 must be a string, ' . gettype($string) . ' given.');
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
* @throws SodiumException
public static function load64_le($string)
if (!is_string($string)) {
throw new TypeError('Argument 1 must be a string, ' . gettype($string) . ' given.');