<?php

if (class_exists('ParagonIE_Sodium_Core32_Ed25519', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core32_Ed25519
 */
abstract class ParagonIE_Sodium_Core32_Ed25519 extends ParagonIE_Sodium_Core32_Curve25519
{
    const KEYPAIR_BYTES = 96;
    const SEED_BYTES = 32;

    /**
     * @internal You should not use this directly from another application
     *
     * @return string (96 bytes)
     * @throws Exception
     * @throws SodiumException
     * @throws TypeError
     */
    public static function keypair()
    {
        $seed = random_bytes(self::SEED_BYTES);
        $pk = '';
        $sk = '';
        self::seed_keypair($pk, $sk, $seed);
        return $sk . $pk;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $pk
     * @param string $sk
     * @param string $seed
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function seed_keypair(&$pk, &$sk, $seed)
    {
        if (self::strlen($seed) !== self::SEED_BYTES) {
            throw new RangeException('crypto_sign keypair seed must be 32 bytes long');
        }

        /** @var string $pk */
        $pk = self::publickey_from_secretkey($seed);
        $sk = $seed . $pk;
        return $sk;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $keypair
     * @return string
     * @throws TypeError
     */
    public static function secretkey($keypair)
    {
        if (self::strlen($keypair) !== self::KEYPAIR_BYTES) {
            throw new RangeException('crypto_sign keypair must be 96 bytes long');
        }
        return self::substr($keypair, 0, 64);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $keypair
     * @return string
     * @throws RangeException
     * @throws TypeError
     */
    public static function publickey($keypair)
    {
        if (self::strlen($keypair) !== self::KEYPAIR_BYTES) {
            throw new RangeException('crypto_sign keypair must be 96 bytes long');
        }
        return self::substr($keypair, 64, 32);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $sk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function publickey_from_secretkey($sk)
    {
        /** @var string $sk */
        $sk = hash('sha512', self::substr($sk, 0, 32), true);
        $sk[0] = self::intToChr(
            self::chrToInt($sk[0]) & 248
        );
        $sk[31] = self::intToChr(
            (self::chrToInt($sk[31]) & 63) | 64
        );
        return self::sk_to_pk($sk);
    }

    /**
     * @param string $pk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function pk_to_curve25519($pk)
    {
        if (self::small_order($pk)) {
            throw new SodiumException('Public key is on a small order');
        }
        $A = self::ge_frombytes_negate_vartime($pk);
        $p1 = self::ge_mul_l($A);
        if (!self::fe_isnonzero($p1->X)) {
            throw new SodiumException('Unexpected zero result');
        }

        # fe_1(one_minus_y);
        # fe_sub(one_minus_y, one_minus_y, A.Y);
        # fe_invert(one_minus_y, one_minus_y);
        $one_minux_y = self::fe_invert(
            self::fe_sub(
                self::fe_1(),
                $A->Y
            )
        );

        # fe_1(x);
        # fe_add(x, x, A.Y);
        # fe_mul(x, x, one_minus_y);
        $x = self::fe_mul(
            self::fe_add(self::fe_1(), $A->Y),
            $one_minux_y
        );

        # fe_tobytes(curve25519_pk, x);
        return self::fe_tobytes($x);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $sk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function sk_to_pk($sk)
    {
        return self::ge_p3_tobytes(
            self::ge_scalarmult_base(
                self::substr($sk, 0, 32)
            )
        );
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $message
     * @param string $sk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function sign($message, $sk)
    {
        /** @var string $signature */
        $signature = self::sign_detached($message, $sk);
        return $signature . $message;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $message A signed message
     * @param string $pk      Public key
     * @return string         Message (without signature)
     * @throws SodiumException
     * @throws TypeError
     */
    public static function sign_open($message, $pk)
    {
        /** @var string $signature */
        $signature = self::substr($message, 0, 64);

        /** @var string $message */
        $message = self::substr($message, 64);

        if (self::verify_detached($signature, $message, $pk)) {
            return $message;
        }
        throw new SodiumException('Invalid signature');
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $message
     * @param string $sk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function sign_detached($message, $sk)
    {
        # crypto_hash_sha512(az, sk, 32);
        $az =  hash('sha512', self::substr($sk, 0, 32), true);

        # az[0] &= 248;
        # az[31] &= 63;
        # az[31] |= 64;
        $az[0] = self::intToChr(self::chrToInt($az[0]) & 248);
        $az[31] = self::intToChr((self::chrToInt($az[31]) & 63) | 64);

        # crypto_hash_sha512_init(&hs);
        # crypto_hash_sha512_update(&hs, az + 32, 32);
        # crypto_hash_sha512_update(&hs, m, mlen);
        # crypto_hash_sha512_final(&hs, nonce);
        $hs = hash_init('sha512');
        hash_update($hs, self::substr($az, 32, 32));
        hash_update($hs, $message);
        $nonceHash = hash_final($hs, true);

        # memmove(sig + 32, sk + 32, 32);
        $pk = self::substr($sk, 32, 32);

        # sc_reduce(nonce);
        # ge_scalarmult_base(&R, nonce);
        # ge_p3_tobytes(sig, &R);
        $nonce = self::sc_reduce($nonceHash) . self::substr($nonceHash, 32);
        $sig = self::ge_p3_tobytes(
            self::ge_scalarmult_base($nonce)
        );

        # crypto_hash_sha512_init(&hs);
        # crypto_hash_sha512_update(&hs, sig, 64);
        # crypto_hash_sha512_update(&hs, m, mlen);
        # crypto_hash_sha512_final(&hs, hram);
        $hs = hash_init('sha512');
        hash_update($hs, self::substr($sig, 0, 32));
        hash_update($hs, self::substr($pk, 0, 32));
        hash_update($hs, $message);
        $hramHash = hash_final($hs, true);

        # sc_reduce(hram);
        # sc_muladd(sig + 32, hram, az, nonce);
        $hram = self::sc_reduce($hramHash);
        $sigAfter = self::sc_muladd($hram, $az, $nonce);
        $sig = self::substr($sig, 0, 32) . self::substr($sigAfter, 0, 32);

        try {
            ParagonIE_Sodium_Compat::memzero($az);
        } catch (SodiumException $ex) {
            $az = null;
        }
        return $sig;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $sig
     * @param string $message
     * @param string $pk
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    public static function verify_detached($sig, $message, $pk)
    {
        if (self::strlen($sig) < 64) {
            throw new SodiumException('Signature is too short');
        }
        if ((self::chrToInt($sig[63]) & 240) && self::check_S_lt_L(self::substr($sig, 32, 32))) {
            throw new SodiumException('S < L - Invalid signature');
        }
        if (self::small_order($sig)) {
            throw new SodiumException('Signature is on too small of an order');
        }
        if ((self::chrToInt($sig[63]) & 224) !== 0) {
            throw new SodiumException('Invalid signature');
        }
        $d = 0;
        for ($i = 0; $i < 32; ++$i) {
            $d |= self::chrToInt($pk[$i]);
        }
        if ($d === 0) {
            throw new SodiumException('All zero public key');
        }

        /** @var bool The original value of ParagonIE_Sodium_Compat::$fastMult */
        $orig = ParagonIE_Sodium_Compat::$fastMult;

        // Set ParagonIE_Sodium_Compat::$fastMult to true to speed up verification.
        ParagonIE_Sodium_Compat::$fastMult = true;

        /** @var ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $A */
        $A = self::ge_frombytes_negate_vartime($pk);

        /** @var string $hDigest */
        $hDigest = hash(
            'sha512',
            self::substr($sig, 0, 32) .
            self::substr($pk, 0, 32) .
            $message,
            true
        );

        /** @var string $h */
        $h = self::sc_reduce($hDigest) . self::substr($hDigest, 32);

        /** @var ParagonIE_Sodium_Core32_Curve25519_Ge_P2 $R */
        $R = self::ge_double_scalarmult_vartime(
            $h,
            $A,
            self::substr($sig, 32)
        );

        /** @var string $rcheck */
        $rcheck = self::ge_tobytes($R);

        // Reset ParagonIE_Sodium_Compat::$fastMult to what it was before.
        ParagonIE_Sodium_Compat::$fastMult = $orig;

        return self::verify_32($rcheck, self::substr($sig, 0, 32));
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $S
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    public static function check_S_lt_L($S)
    {
        if (self::strlen($S) < 32) {
            throw new SodiumException('Signature must be 32 bytes');
        }
        static $L = array(
            0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58,
            0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10
        );
        /** @var array<int, int> $L */
        $c = 0;
        $n = 1;
        $i = 32;

        do {
            --$i;
            $x = self::chrToInt($S[$i]);
            $c |= (
                (($x - $L[$i]) >> 8) & $n
            );
            $n &= (
                (($x ^ $L[$i]) - 1) >> 8
            );
        } while ($i !== 0);

        return $c === 0;
    }

    /**
     * @param string $R
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    public static function small_order($R)
    {
        static $blocklist = array(
            /* 0 (order 4) */
            array(
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
            ),
            /* 1 (order 1) */
            array(
                0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
            ),
            /* 2707385501144840649318225287225658788936804267575313519463743609750303402022 (order 8) */
            array(
                0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0,
                0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98, 0xf0,
                0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39,
                0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x05
            ),
            /* 55188659117513257062467267217118295137698188065244968500265048394206261417927 (order 8) */
            array(
                0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f,
                0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f,
                0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6,
                0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0x7a
            ),
            /* p-1 (order 2) */
            array(
                0x13, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0,
                0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98, 0xf0,
                0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39,
                0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x85
            ),
            /* p (order 4) */
            array(
                0xb4, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f,
                0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f,
                0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6,
                0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0xfa
            ),
            /* p+1 (order 1) */
            array(
                0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f
            ),
            /* p+2707385501144840649318225287225658788936804267575313519463743609750303402022 (order 8) */
            array(
                0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f
            ),
            /* p+55188659117513257062467267217118295137698188065244968500265048394206261417927 (order 8) */
            array(
                0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f
            ),
            /* 2p-1 (order 2) */
            array(
                0xd9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
            ),
            /* 2p (order 4) */
            array(
                0xda, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
            ),
            /* 2p+1 (order 1) */
            array(
                0xdb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
            )
        );
        /** @var array<int, array<int, int>> $blocklist */
        $countBlocklist = count($blocklist);

        for ($i = 0; $i < $countBlocklist; ++$i) {
            $c = 0;
            for ($j = 0; $j < 32; ++$j) {
                $c |= self::chrToInt($R[$j]) ^ $blocklist[$i][$j];
            }
            if ($c === 0) {
                return true;
            }
        }
        return false;
    }
}


<?php

if (class_exists('ParagonIE_Sodium_Core32_HChaCha20', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core_HChaCha20
 */
class ParagonIE_Sodium_Core32_HChaCha20 extends ParagonIE_Sodium_Core32_ChaCha20
{
    /**
     * @param string $in
     * @param string $key
     * @param string|null $c
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function hChaCha20($in = '', $key = '', $c = null)
    {
        $ctx = array();

        if ($c === null) {
            $ctx[0] = new ParagonIE_Sodium_Core32_Int32(array(0x6170, 0x7865));
            $ctx[1] = new ParagonIE_Sodium_Core32_Int32(array(0x3320, 0x646e));
            $ctx[2] = new ParagonIE_Sodium_Core32_Int32(array(0x7962, 0x2d32));
            $ctx[3] = new ParagonIE_Sodium_Core32_Int32(array(0x6b20, 0x6574));
        } else {
            $ctx[0] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($c, 0, 4));
            $ctx[1] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($c, 4, 4));
            $ctx[2] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($c, 8, 4));
            $ctx[3] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($c, 12, 4));
        }
        $ctx[4]  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 0, 4));
        $ctx[5]  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 4, 4));
        $ctx[6]  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 8, 4));
        $ctx[7]  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 12, 4));
        $ctx[8]  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 16, 4));
        $ctx[9]  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 20, 4));
        $ctx[10] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 24, 4));
        $ctx[11] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 28, 4));
        $ctx[12] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($in, 0, 4));
        $ctx[13] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($in, 4, 4));
        $ctx[14] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($in, 8, 4));
        $ctx[15] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($in, 12, 4));

        return self::hChaCha20Bytes($ctx);
    }

    /**
     * @param array $ctx
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    protected static function hChaCha20Bytes(array $ctx)
    {
        /** @var ParagonIE_Sodium_Core32_Int32 $x0 */
        $x0  = $ctx[0];
        /** @var ParagonIE_Sodium_Core32_Int32 $x1 */
        $x1  = $ctx[1];
        /** @var ParagonIE_Sodium_Core32_Int32 $x2 */
        $x2  = $ctx[2];
        /** @var ParagonIE_Sodium_Core32_Int32 $x3 */
        $x3  = $ctx[3];
        /** @var ParagonIE_Sodium_Core32_Int32 $x4 */
        $x4  = $ctx[4];
        /** @var ParagonIE_Sodium_Core32_Int32 $x5 */
        $x5  = $ctx[5];
        /** @var ParagonIE_Sodium_Core32_Int32 $x6 */
        $x6  = $ctx[6];
        /** @var ParagonIE_Sodium_Core32_Int32 $x7 */
        $x7  = $ctx[7];
        /** @var ParagonIE_Sodium_Core32_Int32 $x8 */
        $x8  = $ctx[8];
        /** @var ParagonIE_Sodium_Core32_Int32 $x9 */
        $x9  = $ctx[9];
        /** @var ParagonIE_Sodium_Core32_Int32 $x10 */
        $x10 = $ctx[10];
        /** @var ParagonIE_Sodium_Core32_Int32 $x11 */
        $x11 = $ctx[11];
        /** @var ParagonIE_Sodium_Core32_Int32 $x12 */
        $x12 = $ctx[12];
        /** @var ParagonIE_Sodium_Core32_Int32 $x13 */
        $x13 = $ctx[13];
        /** @var ParagonIE_Sodium_Core32_Int32 $x14 */
        $x14 = $ctx[14];
        /** @var ParagonIE_Sodium_Core32_Int32 $x15 */
        $x15 = $ctx[15];

        for ($i = 0; $i < 10; ++$i) {
            # QUARTERROUND( x0,  x4,  x8,  x12)
            list($x0, $x4, $x8, $x12) = self::quarterRound($x0, $x4, $x8, $x12);

            # QUARTERROUND( x1,  x5,  x9,  x13)
            list($x1, $x5, $x9, $x13) = self::quarterRound($x1, $x5, $x9, $x13);

            # QUARTERROUND( x2,  x6,  x10,  x14)
            list($x2, $x6, $x10, $x14) = self::quarterRound($x2, $x6, $x10, $x14);

            # QUARTERROUND( x3,  x7,  x11,  x15)
            list($x3, $x7, $x11, $x15) = self::quarterRound($x3, $x7, $x11, $x15);

            # QUARTERROUND( x0,  x5,  x10,  x15)
            list($x0, $x5, $x10, $x15) = self::quarterRound($x0, $x5, $x10, $x15);

            # QUARTERROUND( x1,  x6,  x11,  x12)
            list($x1, $x6, $x11, $x12) = self::quarterRound($x1, $x6, $x11, $x12);

            # QUARTERROUND( x2,  x7,  x8,  x13)
            list($x2, $x7, $x8, $x13) = self::quarterRound($x2, $x7, $x8, $x13);

            # QUARTERROUND( x3,  x4,  x9,  x14)
            list($x3, $x4, $x9, $x14) = self::quarterRound($x3, $x4, $x9, $x14);
        }

        return $x0->toReverseString() .
            $x1->toReverseString() .
            $x2->toReverseString() .
            $x3->toReverseString() .
            $x12->toReverseString() .
            $x13->toReverseString() .
            $x14->toReverseString() .
            $x15->toReverseString();
    }
}


<?php

if (class_exists('ParagonIE_Sodium_Core32_HSalsa20', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core32_HSalsa20
 */
abstract class ParagonIE_Sodium_Core32_HSalsa20 extends ParagonIE_Sodium_Core32_Salsa20
{
    /**
     * Calculate an hsalsa20 hash of a single block
     *
     * HSalsa20 doesn't have a counter and will never be used for more than
     * one block (used to derive a subkey for xsalsa20).
     *
     * @internal You should not use this directly from another application
     *
     * @param string $in
     * @param string $k
     * @param string|null $c
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function hsalsa20($in, $k, $c = null)
    {
        /**
         * @var ParagonIE_Sodium_Core32_Int32 $x0
         * @var ParagonIE_Sodium_Core32_Int32 $x1
         * @var ParagonIE_Sodium_Core32_Int32 $x2
         * @var ParagonIE_Sodium_Core32_Int32 $x3
         * @var ParagonIE_Sodium_Core32_Int32 $x4
         * @var ParagonIE_Sodium_Core32_Int32 $x5
         * @var ParagonIE_Sodium_Core32_Int32 $x6
         * @var ParagonIE_Sodium_Core32_Int32 $x7
         * @var ParagonIE_Sodium_Core32_Int32 $x8
         * @var ParagonIE_Sodium_Core32_Int32 $x9
         * @var ParagonIE_Sodium_Core32_Int32 $x10
         * @var ParagonIE_Sodium_Core32_Int32 $x11
         * @var ParagonIE_Sodium_Core32_Int32 $x12
         * @var ParagonIE_Sodium_Core32_Int32 $x13
         * @var ParagonIE_Sodium_Core32_Int32 $x14
         * @var ParagonIE_Sodium_Core32_Int32 $x15
         * @var ParagonIE_Sodium_Core32_Int32 $j0
         * @var ParagonIE_Sodium_Core32_Int32 $j1
         * @var ParagonIE_Sodium_Core32_Int32 $j2
         * @var ParagonIE_Sodium_Core32_Int32 $j3
         * @var ParagonIE_Sodium_Core32_Int32 $j4
         * @var ParagonIE_Sodium_Core32_Int32 $j5
         * @var ParagonIE_Sodium_Core32_Int32 $j6
         * @var ParagonIE_Sodium_Core32_Int32 $j7
         * @var ParagonIE_Sodium_Core32_Int32 $j8
         * @var ParagonIE_Sodium_Core32_Int32 $j9
         * @var ParagonIE_Sodium_Core32_Int32 $j10
         * @var ParagonIE_Sodium_Core32_Int32 $j11
         * @var ParagonIE_Sodium_Core32_Int32 $j12
         * @var ParagonIE_Sodium_Core32_Int32 $j13
         * @var ParagonIE_Sodium_Core32_Int32 $j14
         * @var ParagonIE_Sodium_Core32_Int32 $j15
         */
        if (self::strlen($k) < 32) {
            throw new RangeException('Key must be 32 bytes long');
        }
        if ($c === null) {
            $x0  = new ParagonIE_Sodium_Core32_Int32(array(0x6170, 0x7865));
            $x5  = new ParagonIE_Sodium_Core32_Int32(array(0x3320, 0x646e));
            $x10 = new ParagonIE_Sodium_Core32_Int32(array(0x7962, 0x2d32));
            $x15 = new ParagonIE_Sodium_Core32_Int32(array(0x6b20, 0x6574));
        } else {
            $x0  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($c, 0, 4));
            $x5  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($c, 4, 4));
            $x10 = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($c, 8, 4));
            $x15 = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($c, 12, 4));
        }
        $x1  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 0, 4));
        $x2  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 4, 4));
        $x3  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 8, 4));
        $x4  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 12, 4));
        $x6  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($in, 0, 4));
        $x7  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($in, 4, 4));
        $x8  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($in, 8, 4));
        $x9  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($in, 12, 4));
        $x11 = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 16, 4));
        $x12 = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 20, 4));
        $x13 = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 24, 4));
        $x14 = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 28, 4));

        for ($i = self::ROUNDS; $i > 0; $i -= 2) {
            $x4  = $x4->xorInt32($x0->addInt32($x12)->rotateLeft(7));
            $x8  = $x8->xorInt32($x4->addInt32($x0)->rotateLeft(9));
            $x12 = $x12->xorInt32($x8->addInt32($x4)->rotateLeft(13));
            $x0  = $x0->xorInt32($x12->addInt32($x8)->rotateLeft(18));

            $x9  = $x9->xorInt32($x5->addInt32($x1)->rotateLeft(7));
            $x13 = $x13->xorInt32($x9->addInt32($x5)->rotateLeft(9));
            $x1  = $x1->xorInt32($x13->addInt32($x9)->rotateLeft(13));
            $x5  = $x5->xorInt32($x1->addInt32($x13)->rotateLeft(18));

            $x14 = $x14->xorInt32($x10->addInt32($x6)->rotateLeft(7));
            $x2  = $x2->xorInt32($x14->addInt32($x10)->rotateLeft(9));
            $x6  = $x6->xorInt32($x2->addInt32($x14)->rotateLeft(13));
            $x10 = $x10->xorInt32($x6->addInt32($x2)->rotateLeft(18));

            $x3  = $x3->xorInt32($x15->addInt32($x11)->rotateLeft(7));
            $x7  = $x7->xorInt32($x3->addInt32($x15)->rotateLeft(9));
            $x11 = $x11->xorInt32($x7->addInt32($x3)->rotateLeft(13));
            $x15 = $x15->xorInt32($x11->addInt32($x7)->rotateLeft(18));

            $x1  = $x1->xorInt32($x0->addInt32($x3)->rotateLeft(7));
            $x2  = $x2->xorInt32($x1->addInt32($x0)->rotateLeft(9));
            $x3  = $x3->xorInt32($x2->addInt32($x1)->rotateLeft(13));
            $x0  = $x0->xorInt32($x3->addInt32($x2)->rotateLeft(18));

            $x6  = $x6->xorInt32($x5->addInt32($x4)->rotateLeft(7));
            $x7  = $x7->xorInt32($x6->addInt32($x5)->rotateLeft(9));
            $x4  = $x4->xorInt32($x7->addInt32($x6)->rotateLeft(13));
            $x5  = $x5->xorInt32($x4->addInt32($x7)->rotateLeft(18));

            $x11 = $x11->xorInt32($x10->addInt32($x9)->rotateLeft(7));
            $x8  = $x8->xorInt32($x11->addInt32($x10)->rotateLeft(9));
            $x9  = $x9->xorInt32($x8->addInt32($x11)->rotateLeft(13));
            $x10 = $x10->xorInt32($x9->addInt32($x8)->rotateLeft(18));

            $x12 = $x12->xorInt32($x15->addInt32($x14)->rotateLeft(7));
            $x13 = $x13->xorInt32($x12->addInt32($x15)->rotateLeft(9));
            $x14 = $x14->xorInt32($x13->addInt32($x12)->rotateLeft(13));
            $x15 = $x15->xorInt32($x14->addInt32($x13)->rotateLeft(18));
        }

        return $x0->toReverseString() .
            $x5->toReverseString() .
            $x10->toReverseString() .
            $x15->toReverseString() .
            $x6->toReverseString() .
            $x7->toReverseString() .
            $x8->toReverseString() .
            $x9->toReverseString();
    }
}


<?php

/**
 * Class ParagonIE_Sodium_Core32_Int32
 *
 * Encapsulates a 32-bit integer.
 *
 * These are immutable. It always returns a new instance.
 */
class ParagonIE_Sodium_Core32_Int32
{
    /**
     * @var array<int, int> - two 16-bit integers
     *
     * 0 is the higher 16 bits
     * 1 is the lower 16 bits
     */
    public $limbs = array(0, 0);

    /**
     * @var int
     */
    public $overflow = 0;

    /**
     * @var bool
     */
    public $unsignedInt = false;

    /**
     * ParagonIE_Sodium_Core32_Int32 constructor.
     * @param array $array
     * @param bool $unsignedInt
     */
    public function __construct($array = array(0, 0), $unsignedInt = false)
    {
        $this->limbs = array(
            (int) $array[0],
            (int) $array[1]
        );
        $this->overflow = 0;
        $this->unsignedInt = $unsignedInt;
    }

    /**
     * Adds two int32 objects
     *
     * @param ParagonIE_Sodium_Core32_Int32 $addend
     * @return ParagonIE_Sodium_Core32_Int32
     */
    public function addInt32(ParagonIE_Sodium_Core32_Int32 $addend)
    {
        $i0 = $this->limbs[0];
        $i1 = $this->limbs[1];
        $j0 = $addend->limbs[0];
        $j1 = $addend->limbs[1];

        $r1 = $i1 + ($j1 & 0xffff);
        $carry = $r1 >> 16;

        $r0 = $i0 + ($j0 & 0xffff) + $carry;
        $carry = $r0 >> 16;

        $r0 &= 0xffff;
        $r1 &= 0xffff;

        $return = new ParagonIE_Sodium_Core32_Int32(
            array($r0, $r1)
        );
        $return->overflow = $carry;
        $return->unsignedInt = $this->unsignedInt;
        return $return;
    }

    /**
     * Adds a normal integer to an int32 object
     *
     * @param int $int
     * @return ParagonIE_Sodium_Core32_Int32
     * @throws SodiumException
     * @throws TypeError
     */
    public function addInt($int)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($int, 'int', 1);
        /** @var int $int */
        $int = (int) $int;

        $int = (int) $int;

        $i0 = $this->limbs[0];
        $i1 = $this->limbs[1];

        $r1 = $i1 + ($int & 0xffff);
        $carry = $r1 >> 16;

        $r0 = $i0 + (($int >> 16) & 0xffff) + $carry;
        $carry = $r0 >> 16;
        $r0 &= 0xffff;
        $r1 &= 0xffff;
        $return = new ParagonIE_Sodium_Core32_Int32(
            array($r0, $r1)
        );
        $return->overflow = $carry;
        $return->unsignedInt = $this->unsignedInt;
        return $return;
    }

    /**
     * @param int $b
     * @return int
     */
    public function compareInt($b = 0)
    {
        $gt = 0;
        $eq = 1;

        $i = 2;
        $j = 0;
        while ($i > 0) {
            --$i;
            /** @var int $x1 */
            $x1 = $this->limbs[$i];
            /** @var int $x2 */
            $x2 = ($b >> ($j << 4)) & 0xffff;
            /** @var int $gt */
            $gt |= (($x2 - $x1) >> 8) & $eq;
            /** @var int $eq */
            $eq &= (($x2 ^ $x1) - 1) >> 8;
        }
        return ($gt + $gt - $eq) + 1;
    }

    /**
     * @param int $m
     * @return ParagonIE_Sodium_Core32_Int32
     */
    public function mask($m = 0)
    {
        /** @var int $hi */
        $hi = ($m >> 16) & 0xffff;
        /** @var int $lo */
        $lo = ($m & 0xffff);
        return new ParagonIE_Sodium_Core32_Int32(
            array(
                (int) ($this->limbs[0] & $hi),
                (int) ($this->limbs[1] & $lo)
            ),
            $this->unsignedInt
        );
    }

    /**
     * @param array<int, int> $a
     * @param array<int, int> $b
     * @param int $baseLog2
     * @return array<int, int>
     */
    public function multiplyLong(array $a, array $b, $baseLog2 = 16)
    {
        $a_l = count($a);
        $b_l = count($b);
        /** @var array<int, int> $r */
        $r = array_fill(0, $a_l + $b_l + 1, 0);
        $base = 1 << $baseLog2;
        for ($i = 0; $i < $a_l; ++$i) {
            $a_i = $a[$i];
            for ($j = 0; $j < $a_l; ++$j) {
                $b_j = $b[$j];
                $product = ($a_i * $b_j) + $r[$i + $j];
                $carry = ($product >> $baseLog2 & 0xffff);
                $r[$i + $j] = ($product - (int) ($carry * $base)) & 0xffff;
                $r[$i + $j + 1] += $carry;
            }
        }
        return array_slice($r, 0, 5);
    }

    /**
     * @param int $int
     * @return ParagonIE_Sodium_Core32_Int32
     */
    public function mulIntFast($int)
    {
        // Handle negative numbers
        $aNeg = ($this->limbs[0] >> 15) & 1;
        $bNeg = ($int >> 31) & 1;
        $a = array_reverse($this->limbs);
        $b = array(
            $int & 0xffff,
            ($int >> 16) & 0xffff
        );
        if ($aNeg) {
            for ($i = 0; $i < 2; ++$i) {
                $a[$i] = ($a[$i] ^ 0xffff) & 0xffff;
            }
            ++$a[0];
        }
        if ($bNeg) {
            for ($i = 0; $i < 2; ++$i) {
                $b[$i] = ($b[$i] ^ 0xffff) & 0xffff;
            }
            ++$b[0];
        }
        // Multiply
        $res = $this->multiplyLong($a, $b);

        // Re-apply negation to results
        if ($aNeg !== $bNeg) {
            for ($i = 0; $i < 2; ++$i) {
                $res[$i] = (0xffff ^ $res[$i]) & 0xffff;
            }
            // Handle integer overflow
            $c = 1;
            for ($i = 0; $i < 2; ++$i) {
                $res[$i] += $c;
                $c = $res[$i] >> 16;
                $res[$i] &= 0xffff;
            }
        }

        // Return our values
        $return = new ParagonIE_Sodium_Core32_Int32();
        $return->limbs = array(
            $res[1] & 0xffff,
            $res[0] & 0xffff
        );
        if (count($res) > 2) {
            $return->overflow = $res[2] & 0xffff;
        }
        $return->unsignedInt = $this->unsignedInt;
        return $return;
    }

    /**
     * @param ParagonIE_Sodium_Core32_Int32 $right
     * @return ParagonIE_Sodium_Core32_Int32
     */
    public function mulInt32Fast(ParagonIE_Sodium_Core32_Int32 $right)
    {
        $aNeg = ($this->limbs[0] >> 15) & 1;
        $bNeg = ($right->limbs[0] >> 15) & 1;

        $a = array_reverse($this->limbs);
        $b = array_reverse($right->limbs);
        if ($aNeg) {
            for ($i = 0; $i < 2; ++$i) {
                $a[$i] = ($a[$i] ^ 0xffff) & 0xffff;
            }
            ++$a[0];
        }
        if ($bNeg) {
            for ($i = 0; $i < 2; ++$i) {
                $b[$i] = ($b[$i] ^ 0xffff) & 0xffff;
            }
            ++$b[0];
        }
        $res = $this->multiplyLong($a, $b);
        if ($aNeg !== $bNeg) {
            if ($aNeg !== $bNeg) {
                for ($i = 0; $i < 2; ++$i) {
                    $res[$i] = ($res[$i] ^ 0xffff) & 0xffff;
                }
                $c = 1;
                for ($i = 0; $i < 2; ++$i) {
                    $res[$i] += $c;
                    $c = $res[$i] >> 16;
                    $res[$i] &= 0xffff;
                }
            }
        }
        $return = new ParagonIE_Sodium_Core32_Int32();
        $return->limbs = array(
            $res[1] & 0xffff,
            $res[0] & 0xffff
        );
        if (count($res) > 2) {
            $return->overflow = $res[2];
        }
        return $return;
    }

    /**
     * @param int $int
     * @param int $size
     * @return ParagonIE_Sodium_Core32_Int32
     * @throws SodiumException
     * @throws TypeError
     */
    public function mulInt($int = 0, $size = 0)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($int, 'int', 1);
        ParagonIE_Sodium_Core32_Util::declareScalarType($size, 'int', 2);
        if (ParagonIE_Sodium_Compat::$fastMult) {
            return $this->mulIntFast((int) $int);
        }
        /** @var int $int */
        $int = (int) $int;
        /** @var int $size */
        $size = (int) $size;

        if (!$size) {
            $size = 31;
        }
        /** @var int $size */

        $a = clone $this;
        $return = new ParagonIE_Sodium_Core32_Int32();
        $return->unsignedInt = $this->unsignedInt;

        // Initialize:
        $ret0 = 0;
        $ret1 = 0;
        $a0 = $a->limbs[0];
        $a1 = $a->limbs[1];

        /** @var int $size */
        /** @var int $i */
        for ($i = $size; $i >= 0; --$i) {
            $m = (int) (-($int & 1));
            $x0 = $a0 & $m;
            $x1 = $a1 & $m;

            $ret1 += $x1;
            $c = $ret1 >> 16;

            $ret0 += $x0 + $c;

            $ret0 &= 0xffff;
            $ret1 &= 0xffff;

            $a1 = ($a1 << 1);
            $x1 = $a1 >> 16;
            $a0 = ($a0 << 1) | $x1;
            $a0 &= 0xffff;
            $a1 &= 0xffff;
            $int >>= 1;
        }
        $return->limbs[0] = $ret0;
        $return->limbs[1] = $ret1;
        return $return;
    }

    /**
     * @param ParagonIE_Sodium_Core32_Int32 $int
     * @param int $size
     * @return ParagonIE_Sodium_Core32_Int32
     * @throws SodiumException
     * @throws TypeError
     */
    public function mulInt32(ParagonIE_Sodium_Core32_Int32 $int, $size = 0)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($size, 'int', 2);
        if (ParagonIE_Sodium_Compat::$fastMult) {
            return $this->mulInt32Fast($int);
        }
        if (!$size) {
            $size = 31;
        }
        /** @var int $size */

        $a = clone $this;
        $b = clone $int;
        $return = new ParagonIE_Sodium_Core32_Int32();
        $return->unsignedInt = $this->unsignedInt;

        // Initialize:
        $ret0 = 0;
        $ret1 = 0;
        $a0 = $a->limbs[0];
        $a1 = $a->limbs[1];
        $b0 = $b->limbs[0];
        $b1 = $b->limbs[1];

        /** @var int $size */
        /** @var int $i */
        for ($i = $size; $i >= 0; --$i) {
            $m = (int) (-($b1 & 1));
            $x0 = $a0 & $m;
            $x1 = $a1 & $m;

            $ret1 += $x1;
            $c = $ret1 >> 16;

            $ret0 += $x0 + $c;

            $ret0 &= 0xffff;
            $ret1 &= 0xffff;

            $a1 = ($a1 << 1);
            $x1 = $a1 >> 16;
            $a0 = ($a0 << 1) | $x1;
            $a0 &= 0xffff;
            $a1 &= 0xffff;

            $x0 = ($b0 & 1) << 16;
            $b0 = ($b0 >> 1);
            $b1 = (($b1 | $x0) >> 1);

            $b0 &= 0xffff;
            $b1 &= 0xffff;

        }
        $return->limbs[0] = $ret0;
        $return->limbs[1] = $ret1;

        return $return;
    }

    /**
     * OR this 32-bit integer with another.
     *
     * @param ParagonIE_Sodium_Core32_Int32 $b
     * @return ParagonIE_Sodium_Core32_Int32
     */
    public function orInt32(ParagonIE_Sodium_Core32_Int32 $b)
    {
        $return = new ParagonIE_Sodium_Core32_Int32();
        $return->unsignedInt = $this->unsignedInt;
        $return->limbs = array(
            (int) ($this->limbs[0] | $b->limbs[0]),
            (int) ($this->limbs[1] | $b->limbs[1])
        );
        /** @var int overflow */
        $return->overflow = $this->overflow | $b->overflow;
        return $return;
    }

    /**
     * @param int $b
     * @return bool
     */
    public function isGreaterThan($b = 0)
    {
        return $this->compareInt($b) > 0;
    }

    /**
     * @param int $b
     * @return bool
     */
    public function isLessThanInt($b = 0)
    {
        return $this->compareInt($b) < 0;
    }

    /**
     * @param int $c
     * @return ParagonIE_Sodium_Core32_Int32
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArrayAccess
     */
    public function rotateLeft($c = 0)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($c, 'int', 1);
        /** @var int $c */
        $c = (int) $c;

        $return = new ParagonIE_Sodium_Core32_Int32();
        $return->unsignedInt = $this->unsignedInt;
        $c &= 31;
        if ($c === 0) {
            // NOP, but we want a copy.
            $return->limbs = $this->limbs;
        } else {
            /** @var int $c */

            /** @var int $idx_shift */
            $idx_shift = ($c >> 4) & 1;

            /** @var int $sub_shift */
            $sub_shift = $c & 15;

            /** @var array<int, int> $limbs */
            $limbs =& $return->limbs;

            /** @var array<int, int> $myLimbs */
            $myLimbs =& $this->limbs;

            for ($i = 1; $i >= 0; --$i) {
                /** @var int $j */
                $j = ($i + $idx_shift) & 1;
                /** @var int $k */
                $k = ($i + $idx_shift + 1) & 1;
                $limbs[$i] = (int) (
                    (
                        ((int) ($myLimbs[$j]) << $sub_shift)
                            |
                        ((int) ($myLimbs[$k]) >> (16 - $sub_shift))
                    ) & 0xffff
                );
            }
        }
        return $return;
    }

    /**
     * Rotate to the right
     *
     * @param int $c
     * @return ParagonIE_Sodium_Core32_Int32
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArrayAccess
     */
    public function rotateRight($c = 0)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($c, 'int', 1);
        /** @var int $c */
        $c = (int) $c;

        $return = new ParagonIE_Sodium_Core32_Int32();
        $return->unsignedInt = $this->unsignedInt;
        $c &= 31;
        /** @var int $c */
        if ($c === 0) {
            // NOP, but we want a copy.
            $return->limbs = $this->limbs;
        } else {
            /** @var int $c */

            /** @var int $idx_shift */
            $idx_shift = ($c >> 4) & 1;

            /** @var int $sub_shift */
            $sub_shift = $c & 15;

            /** @var array<int, int> $limbs */
            $limbs =& $return->limbs;

            /** @var array<int, int> $myLimbs */
            $myLimbs =& $this->limbs;

            for ($i = 1; $i >= 0; --$i) {
                /** @var int $j */
                $j = ($i - $idx_shift) & 1;
                /** @var int $k */
                $k = ($i - $idx_shift - 1) & 1;
                $limbs[$i] = (int) (
                    (
                        ((int) ($myLimbs[$j]) >> (int) ($sub_shift))
                            |
                        ((int) ($myLimbs[$k]) << (16 - (int) ($sub_shift)))
                    ) & 0xffff
                );
            }
        }
        return $return;
    }

    /**
     * @param bool $bool
     * @return self
     */
    public function setUnsignedInt($bool = false)
    {
        $this->unsignedInt = !empty($bool);
        return $this;
    }

    /**
     * @param int $c
     * @return ParagonIE_Sodium_Core32_Int32
     * @throws SodiumException
     * @throws TypeError
     */
    public function shiftLeft($c = 0)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($c, 'int', 1);
        /** @var int $c */
        $c = (int) $c;

        $return = new ParagonIE_Sodium_Core32_Int32();
        $return->unsignedInt = $this->unsignedInt;
        $c &= 63;
        /** @var int $c */
        if ($c === 0) {
            $return->limbs = $this->limbs;
        } elseif ($c < 0) {
            /** @var int $c */
            return $this->shiftRight(-$c);
        } else {
            /** @var int $c */
            /** @var int $tmp */
            $tmp = $this->limbs[1] << $c;
            $return->limbs[1] = (int)($tmp & 0xffff);
            /** @var int $carry */
            $carry = $tmp >> 16;

            /** @var int $tmp */
            $tmp = ($this->limbs[0] << $c) | ($carry & 0xffff);
            $return->limbs[0] = (int) ($tmp & 0xffff);
        }
        return $return;
    }

    /**
     * @param int $c
     * @return ParagonIE_Sodium_Core32_Int32
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedAssignment
     * @psalm-suppress MixedOperand
     */
    public function shiftRight($c = 0)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($c, 'int', 1);
        /** @var int $c */
        $c = (int) $c;

        $return = new ParagonIE_Sodium_Core32_Int32();
        $return->unsignedInt = $this->unsignedInt;
        $c &= 63;
        /** @var int $c */
        if ($c >= 16) {
            $return->limbs = array(
                (int) ($this->overflow & 0xffff),
                (int) ($this->limbs[0])
            );
            $return->overflow = $this->overflow >> 16;
            return $return->shiftRight($c & 15);
        }
        if ($c === 0) {
            $return->limbs = $this->limbs;
        } elseif ($c < 0) {
            /** @var int $c */
            return $this->shiftLeft(-$c);
        } else {
            if (!is_int($c)) {
                throw new TypeError();
            }
            /** @var int $c */
            // $return->limbs[0] = (int) (($this->limbs[0] >> $c) & 0xffff);
            $carryLeft = (int) ($this->overflow & ((1 << ($c + 1)) - 1));
            $return->limbs[0] = (int) ((($this->limbs[0] >> $c) | ($carryLeft << (16 - $c))) & 0xffff);
            $carryRight = (int) ($this->limbs[0] & ((1 << ($c + 1)) - 1));
            $return->limbs[1] = (int) ((($this->limbs[1] >> $c) | ($carryRight << (16 - $c))) & 0xffff);
            $return->overflow >>= $c;
        }
        return $return;
    }

    /**
     * Subtract a normal integer from an int32 object.
     *
     * @param int $int
     * @return ParagonIE_Sodium_Core32_Int32
     * @throws SodiumException
     * @throws TypeError
     */
    public function subInt($int)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($int, 'int', 1);
        /** @var int $int */
        $int = (int) $int;

        $return = new ParagonIE_Sodium_Core32_Int32();
        $return->unsignedInt = $this->unsignedInt;

        /** @var int $tmp */
        $tmp = $this->limbs[1] - ($int & 0xffff);
        /** @var int $carry */
        $carry = $tmp >> 16;
        $return->limbs[1] = (int) ($tmp & 0xffff);

        /** @var int $tmp */
        $tmp = $this->limbs[0] - (($int >> 16) & 0xffff) + $carry;
        $return->limbs[0] = (int) ($tmp & 0xffff);
        return $return;
    }

    /**
     * Subtract two int32 objects from each other
     *
     * @param ParagonIE_Sodium_Core32_Int32 $b
     * @return ParagonIE_Sodium_Core32_Int32
     */
    public function subInt32(ParagonIE_Sodium_Core32_Int32 $b)
    {
        $return = new ParagonIE_Sodium_Core32_Int32();
        $return->unsignedInt = $this->unsignedInt;

        /** @var int $tmp */
        $tmp = $this->limbs[1] - ($b->limbs[1] & 0xffff);
        /** @var int $carry */
        $carry = $tmp >> 16;
        $return->limbs[1] = (int) ($tmp & 0xffff);

        /** @var int $tmp */
        $tmp = $this->limbs[0] - ($b->limbs[0] & 0xffff) + $carry;
        $return->limbs[0] = (int) ($tmp & 0xffff);
        return $return;
    }

    /**
     * XOR this 32-bit integer with another.
     *
     * @param ParagonIE_Sodium_Core32_Int32 $b
     * @return ParagonIE_Sodium_Core32_Int32
     */
    public function xorInt32(ParagonIE_Sodium_Core32_Int32 $b)
    {
        $return = new ParagonIE_Sodium_Core32_Int32();
        $return->unsignedInt = $this->unsignedInt;
        $return->limbs = array(
            (int) ($this->limbs[0] ^ $b->limbs[0]),
            (int) ($this->limbs[1] ^ $b->limbs[1])
        );
        return $return;
    }

    /**
     * @param int $signed
     * @return self
     * @throws SodiumException
     * @throws TypeError
     */
    public static function fromInt($signed)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($signed, 'int', 1);;
        /** @var int $signed */
        $signed = (int) $signed;

        return new ParagonIE_Sodium_Core32_Int32(
            array(
                (int) (($signed >> 16) & 0xffff),
                (int) ($signed & 0xffff)
            )
        );
    }

    /**
     * @param string $string
     * @return self
     * @throws SodiumException
     * @throws TypeError
     */
    public static function fromString($string)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($string, 'string', 1);
        $string = (string) $string;
        if (ParagonIE_Sodium_Core32_Util::strlen($string) !== 4) {
            throw new RangeException(
                'String must be 4 bytes; ' . ParagonIE_Sodium_Core32_Util::strlen($string) . ' given.'
            );
        }
        $return = new ParagonIE_Sodium_Core32_Int32();

        $return->limbs[0]  = (int) ((ParagonIE_Sodium_Core32_Util::chrToInt($string[0]) & 0xff) << 8);
        $return->limbs[0] |= (ParagonIE_Sodium_Core32_Util::chrToInt($string[1]) & 0xff);
        $return->limbs[1]  = (int) ((ParagonIE_Sodium_Core32_Util::chrToInt($string[2]) & 0xff) << 8);
        $return->limbs[1] |= (ParagonIE_Sodium_Core32_Util::chrToInt($string[3]) & 0xff);
        return $return;
    }

    /**
     * @param string $string
     * @return self
     * @throws SodiumException
     * @throws TypeError
     */
    public static function fromReverseString($string)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($string, 'string', 1);
        $string = (string) $string;
        if (ParagonIE_Sodium_Core32_Util::strlen($string) !== 4) {
            throw new RangeException(
                'String must be 4 bytes; ' . ParagonIE_Sodium_Core32_Util::strlen($string) . ' given.'
            );
        }
        $return = new ParagonIE_Sodium_Core32_Int32();

        $return->limbs[0]  = (int) ((ParagonIE_Sodium_Core32_Util::chrToInt($string[3]) & 0xff) << 8);
        $return->limbs[0] |= (ParagonIE_Sodium_Core32_Util::chrToInt($string[2]) & 0xff);
        $return->limbs[1]  = (int) ((ParagonIE_Sodium_Core32_Util::chrToInt($string[1]) & 0xff) << 8);
        $return->limbs[1] |= (ParagonIE_Sodium_Core32_Util::chrToInt($string[0]) & 0xff);
        return $return;
    }

    /**
     * @return array<int, int>
     */
    public function toArray()
    {
        return array((int) ($this->limbs[0] << 16 | $this->limbs[1]));
    }

    /**
     * @return string
     * @throws TypeError
     */
    public function toString()
    {
        return
            ParagonIE_Sodium_Core32_Util::intToChr(($this->limbs[0] >> 8) & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr($this->limbs[0] & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr(($this->limbs[1] >> 8) & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr($this->limbs[1] & 0xff);
    }

    /**
     * @return int
     */
    public function toInt()
    {
        return (int) (
            (($this->limbs[0] & 0xffff) << 16)
                |
            ($this->limbs[1] & 0xffff)
        );
    }

    /**
     * @return ParagonIE_Sodium_Core32_Int32
     */
    public function toInt32()
    {
        $return = new ParagonIE_Sodium_Core32_Int32();
        $return->limbs[0] = (int) ($this->limbs[0] & 0xffff);
        $return->limbs[1] = (int) ($this->limbs[1] & 0xffff);
        $return->unsignedInt = $this->unsignedInt;
        $return->overflow = (int) ($this->overflow & 0x7fffffff);
        return $return;
    }

    /**
     * @return ParagonIE_Sodium_Core32_Int64
     */
    public function toInt64()
    {
        $return = new ParagonIE_Sodium_Core32_Int64();
        $return->unsignedInt = $this->unsignedInt;
        if ($this->unsignedInt) {
            $return->limbs[0] += (($this->overflow >> 16) & 0xffff);
            $return->limbs[1] += (($this->overflow) & 0xffff);
        } else {
            $neg = -(($this->limbs[0] >> 15) & 1);
            $return->limbs[0] = (int)($neg & 0xffff);
            $return->limbs[1] = (int)($neg & 0xffff);
        }
        $return->limbs[2] = (int) ($this->limbs[0] & 0xffff);
        $return->limbs[3] = (int) ($this->limbs[1] & 0xffff);
        return $return;
    }

    /**
     * @return string
     * @throws TypeError
     */
    public function toReverseString()
    {
        return ParagonIE_Sodium_Core32_Util::intToChr($this->limbs[1] & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr(($this->limbs[1] >> 8) & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr($this->limbs[0] & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr(($this->limbs[0] >> 8) & 0xff);
    }

    /**
     * @return string
     */
    public function __toString()
    {
        try {
            return $this->toString();
        } catch (TypeError $ex) {
            // PHP engine can't handle exceptions from __toString()
            return '';
        }
    }
}


<?php

/**
 * Class ParagonIE_Sodium_Core32_Int64
 *
 * Encapsulates a 64-bit integer.
 *
 * These are immutable. It always returns a new instance.
 */
class ParagonIE_Sodium_Core32_Int64
{
    /**
     * @var array<int, int> - four 16-bit integers
     */
    public $limbs = array(0, 0, 0, 0);

    /**
     * @var int
     */
    public $overflow = 0;

    /**
     * @var bool
     */
    public $unsignedInt = false;

    /**
     * ParagonIE_Sodium_Core32_Int64 constructor.
     * @param array $array
     * @param bool $unsignedInt
     */
    public function __construct($array = array(0, 0, 0, 0), $unsignedInt = false)
    {
        $this->limbs = array(
            (int) $array[0],
            (int) $array[1],
            (int) $array[2],
            (int) $array[3]
        );
        $this->overflow = 0;
        $this->unsignedInt = $unsignedInt;
    }

    /**
     * Adds two int64 objects
     *
     * @param ParagonIE_Sodium_Core32_Int64 $addend
     * @return ParagonIE_Sodium_Core32_Int64
     */
    public function addInt64(ParagonIE_Sodium_Core32_Int64 $addend)
    {
        $i0 = $this->limbs[0];
        $i1 = $this->limbs[1];
        $i2 = $this->limbs[2];
        $i3 = $this->limbs[3];
        $j0 = $addend->limbs[0];
        $j1 = $addend->limbs[1];
        $j2 = $addend->limbs[2];
        $j3 = $addend->limbs[3];

        $r3 = $i3 + ($j3 & 0xffff);
        $carry = $r3 >> 16;

        $r2 = $i2 + ($j2 & 0xffff) + $carry;
        $carry = $r2 >> 16;

        $r1 = $i1 + ($j1 & 0xffff) + $carry;
        $carry = $r1 >> 16;

        $r0 = $i0 + ($j0 & 0xffff) + $carry;
        $carry = $r0 >> 16;

        $r0 &= 0xffff;
        $r1 &= 0xffff;
        $r2 &= 0xffff;
        $r3 &= 0xffff;

        $return = new ParagonIE_Sodium_Core32_Int64(
            array($r0, $r1, $r2, $r3)
        );
        $return->overflow = $carry;
        $return->unsignedInt = $this->unsignedInt;
        return $return;
    }

    /**
     * Adds a normal integer to an int64 object
     *
     * @param int $int
     * @return ParagonIE_Sodium_Core32_Int64
     * @throws SodiumException
     * @throws TypeError
     */
    public function addInt($int)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($int, 'int', 1);
        /** @var int $int */
        $int = (int) $int;

        $i0 = $this->limbs[0];
        $i1 = $this->limbs[1];
        $i2 = $this->limbs[2];
        $i3 = $this->limbs[3];

        $r3 = $i3 + ($int & 0xffff);
        $carry = $r3 >> 16;

        $r2 = $i2 + (($int >> 16) & 0xffff) + $carry;
        $carry = $r2 >> 16;

        $r1 = $i1 + $carry;
        $carry = $r1 >> 16;

        $r0 = $i0 + $carry;
        $carry = $r0 >> 16;

        $r0 &= 0xffff;
        $r1 &= 0xffff;
        $r2 &= 0xffff;
        $r3 &= 0xffff;
        $return = new ParagonIE_Sodium_Core32_Int64(
            array($r0, $r1, $r2, $r3)
        );
        $return->overflow = $carry;
        $return->unsignedInt = $this->unsignedInt;
        return $return;
    }

    /**
     * @param int $b
     * @return int
     */
    public function compareInt($b = 0)
    {
        $gt = 0;
        $eq = 1;

        $i = 4;
        $j = 0;
        while ($i > 0) {
            --$i;
            /** @var int $x1 */
            $x1 = $this->limbs[$i];
            /** @var int $x2 */
            $x2 = ($b >> ($j << 4)) & 0xffff;
            /** int */
            $gt |= (($x2 - $x1) >> 8) & $eq;
            /** int */
            $eq &= (($x2 ^ $x1) - 1) >> 8;
        }
        return ($gt + $gt - $eq) + 1;
    }

    /**
     * @param int $b
     * @return bool
     */
    public function isGreaterThan($b = 0)
    {
        return $this->compareInt($b) > 0;
    }

    /**
     * @param int $b
     * @return bool
     */
    public function isLessThanInt($b = 0)
    {
        return $this->compareInt($b) < 0;
    }

    /**
     * @param int $hi
     * @param int $lo
     * @return ParagonIE_Sodium_Core32_Int64
     */
    public function mask64($hi = 0, $lo = 0)
    {
        /** @var int $a */
        $a = ($hi >> 16) & 0xffff;
        /** @var int $b */
        $b = ($hi) & 0xffff;
        /** @var int $c */
        $c = ($lo >> 16) & 0xffff;
        /** @var int $d */
        $d = ($lo & 0xffff);
        return new ParagonIE_Sodium_Core32_Int64(
            array(
                $this->limbs[0] & $a,
                $this->limbs[1] & $b,
                $this->limbs[2] & $c,
                $this->limbs[3] & $d
            ),
            $this->unsignedInt
        );
    }

    /**
     * @param int $int
     * @param int $size
     * @return ParagonIE_Sodium_Core32_Int64
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedAssignment
     */
    public function mulInt($int = 0, $size = 0)
    {
        if (ParagonIE_Sodium_Compat::$fastMult) {
            return $this->mulIntFast($int);
        }
        ParagonIE_Sodium_Core32_Util::declareScalarType($int, 'int', 1);
        ParagonIE_Sodium_Core32_Util::declareScalarType($size, 'int', 2);
        /** @var int $int */
        $int = (int) $int;
        /** @var int $size */
        $size = (int) $size;

        if (!$size) {
            $size = 63;
        }

        $a = clone $this;
        $return = new ParagonIE_Sodium_Core32_Int64();
        $return->unsignedInt = $this->unsignedInt;

        // Initialize:
        $ret0 = 0;
        $ret1 = 0;
        $ret2 = 0;
        $ret3 = 0;
        $a0 = $a->limbs[0];
        $a1 = $a->limbs[1];
        $a2 = $a->limbs[2];
        $a3 = $a->limbs[3];

        /** @var int $size */
        /** @var int $i */
        for ($i = $size; $i >= 0; --$i) {
            $mask = -($int & 1);
            $x0 = $a0 & $mask;
            $x1 = $a1 & $mask;
            $x2 = $a2 & $mask;
            $x3 = $a3 & $mask;

            $ret3 += $x3;
            $c = $ret3 >> 16;

            $ret2 += $x2 + $c;
            $c = $ret2 >> 16;

            $ret1 += $x1 + $c;
            $c = $ret1 >> 16;

            $ret0 += $x0 + $c;

            $ret0 &= 0xffff;
            $ret1 &= 0xffff;
            $ret2 &= 0xffff;
            $ret3 &= 0xffff;

            $a3 = $a3 << 1;
            $x3 = $a3 >> 16;
            $a2 = ($a2 << 1) | $x3;
            $x2 = $a2 >> 16;
            $a1 = ($a1 << 1) | $x2;
            $x1 = $a1 >> 16;
            $a0 = ($a0 << 1) | $x1;
            $a0 &= 0xffff;
            $a1 &= 0xffff;
            $a2 &= 0xffff;
            $a3 &= 0xffff;

            $int >>= 1;
        }
        $return->limbs[0] = $ret0;
        $return->limbs[1] = $ret1;
        $return->limbs[2] = $ret2;
        $return->limbs[3] = $ret3;
        return $return;
    }

    /**
     * @param ParagonIE_Sodium_Core32_Int64 $A
     * @param ParagonIE_Sodium_Core32_Int64 $B
     * @return array<int, ParagonIE_Sodium_Core32_Int64>
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedInferredReturnType
     */
    public static function ctSelect(
        ParagonIE_Sodium_Core32_Int64 $A,
        ParagonIE_Sodium_Core32_Int64 $B
    ) {
        $a = clone $A;
        $b = clone $B;
        /** @var int $aNeg */
        $aNeg = ($a->limbs[0] >> 15) & 1;
        /** @var int $bNeg */
        $bNeg = ($b->limbs[0] >> 15) & 1;
        /** @var int $m */
        $m = (-($aNeg & $bNeg)) | 1;
        /** @var int $swap */
        $swap = $bNeg & ~$aNeg;
        /** @var int $d */
        $d = -$swap;

        /*
        if ($bNeg && !$aNeg) {
            $a = clone $int;
            $b = clone $this;
        } elseif($bNeg && $aNeg) {
            $a = $this->mulInt(-1);
            $b = $int->mulInt(-1);
        }
         */
        $x = $a->xorInt64($b)->mask64($d, $d);
        return array(
            $a->xorInt64($x)->mulInt($m),
            $b->xorInt64($x)->mulInt($m)
        );
    }

    /**
     * @param array<int, int> $a
     * @param array<int, int> $b
     * @param int $baseLog2
     * @return array<int, int>
     */
    public function multiplyLong(array $a, array $b, $baseLog2 = 16)
    {
        $a_l = count($a);
        $b_l = count($b);
        /** @var array<int, int> $r */
        $r = array_fill(0, $a_l + $b_l + 1, 0);
        $base = 1 << $baseLog2;
        for ($i = 0; $i < $a_l; ++$i) {
            $a_i = $a[$i];
            for ($j = 0; $j < $a_l; ++$j) {
                $b_j = $b[$j];
                $product = ($a_i * $b_j) + $r[$i + $j];
                $carry = ($product >> $baseLog2 & 0xffff);
                $r[$i + $j] = ($product - (int) ($carry * $base)) & 0xffff;
                $r[$i + $j + 1] += $carry;
            }
        }
        return array_slice($r, 0, 5);
    }

    /**
     * @param int $int
     * @return ParagonIE_Sodium_Core32_Int64
     */
    public function mulIntFast($int)
    {
        // Handle negative numbers
        $aNeg = ($this->limbs[0] >> 15) & 1;
        $bNeg = ($int >> 31) & 1;
        $a = array_reverse($this->limbs);
        $b = array(
            $int & 0xffff,
            ($int >> 16) & 0xffff,
            -$bNeg & 0xffff,
            -$bNeg & 0xffff
        );
        if ($aNeg) {
            for ($i = 0; $i < 4; ++$i) {
                $a[$i] = ($a[$i] ^ 0xffff) & 0xffff;
            }
            ++$a[0];
        }
        if ($bNeg) {
            for ($i = 0; $i < 4; ++$i) {
                $b[$i] = ($b[$i] ^ 0xffff) & 0xffff;
            }
            ++$b[0];
        }
        // Multiply
        $res = $this->multiplyLong($a, $b);

        // Re-apply negation to results
        if ($aNeg !== $bNeg) {
            for ($i = 0; $i < 4; ++$i) {
                $res[$i] = (0xffff ^ $res[$i]) & 0xffff;
            }
            // Handle integer overflow
            $c = 1;
            for ($i = 0; $i < 4; ++$i) {
                $res[$i] += $c;
                $c = $res[$i] >> 16;
                $res[$i] &= 0xffff;
            }
        }

        // Return our values
        $return = new ParagonIE_Sodium_Core32_Int64();
        $return->limbs = array(
            $res[3] & 0xffff,
            $res[2] & 0xffff,
            $res[1] & 0xffff,
            $res[0] & 0xffff
        );
        if (count($res) > 4) {
            $return->overflow = $res[4] & 0xffff;
        }
        $return->unsignedInt = $this->unsignedInt;
        return $return;
    }

    /**
     * @param ParagonIE_Sodium_Core32_Int64 $right
     * @return ParagonIE_Sodium_Core32_Int64
     */
    public function mulInt64Fast(ParagonIE_Sodium_Core32_Int64 $right)
    {
        $aNeg = ($this->limbs[0] >> 15) & 1;
        $bNeg = ($right->limbs[0] >> 15) & 1;

        $a = array_reverse($this->limbs);
        $b = array_reverse($right->limbs);
        if ($aNeg) {
            for ($i = 0; $i < 4; ++$i) {
                $a[$i] = ($a[$i] ^ 0xffff) & 0xffff;
            }
            ++$a[0];
        }
        if ($bNeg) {
            for ($i = 0; $i < 4; ++$i) {
                $b[$i] = ($b[$i] ^ 0xffff) & 0xffff;
            }
            ++$b[0];
        }
        $res = $this->multiplyLong($a, $b);
        if ($aNeg !== $bNeg) {
            if ($aNeg !== $bNeg) {
                for ($i = 0; $i < 4; ++$i) {
                    $res[$i] = ($res[$i] ^ 0xffff) & 0xffff;
                }
                $c = 1;
                for ($i = 0; $i < 4; ++$i) {
                    $res[$i] += $c;
                    $c = $res[$i] >> 16;
                    $res[$i] &= 0xffff;
                }
            }
        }
        $return = new ParagonIE_Sodium_Core32_Int64();
        $return->limbs = array(
            $res[3] & 0xffff,
            $res[2] & 0xffff,
            $res[1] & 0xffff,
            $res[0] & 0xffff
        );
        if (count($res) > 4) {
            $return->overflow = $res[4];
        }
        return $return;
    }

    /**
     * @param ParagonIE_Sodium_Core32_Int64 $int
     * @param int $size
     * @return ParagonIE_Sodium_Core32_Int64
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedAssignment
     */
    public function mulInt64(ParagonIE_Sodium_Core32_Int64 $int, $size = 0)
    {
        if (ParagonIE_Sodium_Compat::$fastMult) {
            return $this->mulInt64Fast($int);
        }
        ParagonIE_Sodium_Core32_Util::declareScalarType($size, 'int', 2);
        if (!$size) {
            $size = 63;
        }
        list($a, $b) = self::ctSelect($this, $int);

        $return = new ParagonIE_Sodium_Core32_Int64();
        $return->unsignedInt = $this->unsignedInt;

        // Initialize:
        $ret0 = 0;
        $ret1 = 0;
        $ret2 = 0;
        $ret3 = 0;
        $a0 = $a->limbs[0];
        $a1 = $a->limbs[1];
        $a2 = $a->limbs[2];
        $a3 = $a->limbs[3];
        $b0 = $b->limbs[0];
        $b1 = $b->limbs[1];
        $b2 = $b->limbs[2];
        $b3 = $b->limbs[3];

        /** @var int $size */
        /** @var int $i */
        for ($i = (int) $size; $i >= 0; --$i) {
            $mask = -($b3 & 1);
            $x0 = $a0 & $mask;
            $x1 = $a1 & $mask;
            $x2 = $a2 & $mask;
            $x3 = $a3 & $mask;

            $ret3 += $x3;
            $c = $ret3 >> 16;

            $ret2 += $x2 + $c;
            $c = $ret2 >> 16;

            $ret1 += $x1 + $c;
            $c = $ret1 >> 16;

            $ret0 += $x0 + $c;

            $ret0 &= 0xffff;
            $ret1 &= 0xffff;
            $ret2 &= 0xffff;
            $ret3 &= 0xffff;

            $a3 = $a3 << 1;
            $x3 = $a3 >> 16;
            $a2 = ($a2 << 1) | $x3;
            $x2 = $a2 >> 16;
            $a1 = ($a1 << 1) | $x2;
            $x1 = $a1 >> 16;
            $a0 = ($a0 << 1) | $x1;
            $a0 &= 0xffff;
            $a1 &= 0xffff;
            $a2 &= 0xffff;
            $a3 &= 0xffff;

            $x0 = ($b0 & 1) << 16;
            $x1 = ($b1 & 1) << 16;
            $x2 = ($b2 & 1) << 16;

            $b0 = ($b0 >> 1);
            $b1 = (($b1 | $x0) >> 1);
            $b2 = (($b2 | $x1) >> 1);
            $b3 = (($b3 | $x2) >> 1);

            $b0 &= 0xffff;
            $b1 &= 0xffff;
            $b2 &= 0xffff;
            $b3 &= 0xffff;

        }
        $return->limbs[0] = $ret0;
        $return->limbs[1] = $ret1;
        $return->limbs[2] = $ret2;
        $return->limbs[3] = $ret3;

        return $return;
    }

    /**
     * OR this 64-bit integer with another.
     *
     * @param ParagonIE_Sodium_Core32_Int64 $b
     * @return ParagonIE_Sodium_Core32_Int64
     */
    public function orInt64(ParagonIE_Sodium_Core32_Int64 $b)
    {
        $return = new ParagonIE_Sodium_Core32_Int64();
        $return->unsignedInt = $this->unsignedInt;
        $return->limbs = array(
            (int) ($this->limbs[0] | $b->limbs[0]),
            (int) ($this->limbs[1] | $b->limbs[1]),
            (int) ($this->limbs[2] | $b->limbs[2]),
            (int) ($this->limbs[3] | $b->limbs[3])
        );
        return $return;
    }

    /**
     * @param int $c
     * @return ParagonIE_Sodium_Core32_Int64
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArrayAccess
     */
    public function rotateLeft($c = 0)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($c, 'int', 1);
        /** @var int $c */
        $c = (int) $c;

        $return = new ParagonIE_Sodium_Core32_Int64();
        $return->unsignedInt = $this->unsignedInt;
        $c &= 63;
        if ($c === 0) {
            // NOP, but we want a copy.
            $return->limbs = $this->limbs;
        } else {
            /** @var array<int, int> $limbs */
            $limbs =& $return->limbs;

            /** @var array<int, int> $myLimbs */
            $myLimbs =& $this->limbs;

            /** @var int $idx_shift */
            $idx_shift = ($c >> 4) & 3;
            /** @var int $sub_shift */
            $sub_shift = $c & 15;

            for ($i = 3; $i >= 0; --$i) {
                /** @var int $j */
                $j = ($i + $idx_shift) & 3;
                /** @var int $k */
                $k = ($i + $idx_shift + 1) & 3;
                $limbs[$i] = (int) (
                    (
                        ((int) ($myLimbs[$j]) << $sub_shift)
                            |
                        ((int) ($myLimbs[$k]) >> (16 - $sub_shift))
                    ) & 0xffff
                );
            }
        }
        return $return;
    }

    /**
     * Rotate to the right
     *
     * @param int $c
     * @return ParagonIE_Sodium_Core32_Int64
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArrayAccess
     */
    public function rotateRight($c = 0)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($c, 'int', 1);
        /** @var int $c */
        $c = (int) $c;

        /** @var ParagonIE_Sodium_Core32_Int64 $return */
        $return = new ParagonIE_Sodium_Core32_Int64();
        $return->unsignedInt = $this->unsignedInt;
        $c &= 63;
        /** @var int $c */
        if ($c === 0) {
            // NOP, but we want a copy.
            $return->limbs = $this->limbs;
        } else {
            /** @var array<int, int> $limbs */
            $limbs =& $return->limbs;

            /** @var array<int, int> $myLimbs */
            $myLimbs =& $this->limbs;

            /** @var int $idx_shift */
            $idx_shift = ($c >> 4) & 3;
            /** @var int $sub_shift */
            $sub_shift = $c & 15;

            for ($i = 3; $i >= 0; --$i) {
                /** @var int $j */
                $j = ($i - $idx_shift) & 3;
                /** @var int $k */
                $k = ($i - $idx_shift - 1) & 3;
                $limbs[$i] = (int) (
                    (
                        ((int) ($myLimbs[$j]) >> (int) ($sub_shift))
                            |
                        ((int) ($myLimbs[$k]) << (16 - (int) ($sub_shift)))
                    ) & 0xffff
                );
            }
        }
        return $return;
    }
    /**
     * @param int $c
     * @return ParagonIE_Sodium_Core32_Int64
     * @throws SodiumException
     * @throws TypeError
     */
    public function shiftLeft($c = 0)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($c, 'int', 1);
        /** @var int $c */
        $c = (int) $c;

        $return = new ParagonIE_Sodium_Core32_Int64();
        $return->unsignedInt = $this->unsignedInt;
        $c &= 63;

        if ($c >= 16) {
            if ($c >= 48) {
                $return->limbs = array(
                    $this->limbs[3], 0, 0, 0
                );
            } elseif ($c >= 32) {
                $return->limbs = array(
                    $this->limbs[2], $this->limbs[3], 0, 0
                );
            } else {
                $return->limbs = array(
                    $this->limbs[1], $this->limbs[2], $this->limbs[3], 0
                );
            }
            return $return->shiftLeft($c & 15);
        }
        if ($c === 0) {
            $return->limbs = $this->limbs;
        } elseif ($c < 0) {
            /** @var int $c */
            return $this->shiftRight(-$c);
        } else {
            if (!is_int($c)) {
                throw new TypeError();
            }
            /** @var int $carry */
            $carry = 0;
            for ($i = 3; $i >= 0; --$i) {
                /** @var int $tmp */
                $tmp = ($this->limbs[$i] << $c) | ($carry & 0xffff);
                $return->limbs[$i] = (int) ($tmp & 0xffff);
                /** @var int $carry */
                $carry = $tmp >> 16;
            }
        }
        return $return;
    }

    /**
     * @param int $c
     * @return ParagonIE_Sodium_Core32_Int64
     * @throws SodiumException
     * @throws TypeError
     */
    public function shiftRight($c = 0)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($c, 'int', 1);
        $c = (int) $c;
        /** @var int $c */
        $return = new ParagonIE_Sodium_Core32_Int64();
        $return->unsignedInt = $this->unsignedInt;
        $c &= 63;

        $negative = -(($this->limbs[0] >> 15) & 1);
        if ($c >= 16) {
            if ($c >= 48) {
                $return->limbs = array(
                    (int) ($negative & 0xffff),
                    (int) ($negative & 0xffff),
                    (int) ($negative & 0xffff),
                    (int) $this->limbs[0]
                );
            } elseif ($c >= 32) {
                $return->limbs = array(
                    (int) ($negative & 0xffff),
                    (int) ($negative & 0xffff),
                    (int) $this->limbs[0],
                    (int) $this->limbs[1]
                );
            } else {
                $return->limbs = array(
                    (int) ($negative & 0xffff),
                    (int) $this->limbs[0],
                    (int) $this->limbs[1],
                    (int) $this->limbs[2]
                );
            }
            return $return->shiftRight($c & 15);
        }

        if ($c === 0) {
            $return->limbs = $this->limbs;
        } elseif ($c < 0) {
            return $this->shiftLeft(-$c);
        } else {
            if (!is_int($c)) {
                throw new TypeError();
            }
            /** @var int $carryRight */
            $carryRight = ($negative & 0xffff);
            $mask = (int) (((1 << ($c + 1)) - 1) & 0xffff);
            for ($i = 0; $i < 4; ++$i) {
                $return->limbs[$i] = (int) (
                    (($this->limbs[$i] >> $c) | ($carryRight << (16 - $c))) & 0xffff
                );
                $carryRight = (int) ($this->limbs[$i] & $mask);
            }
        }
        return $return;
    }

    /**
     * Subtract a normal integer from an int64 object.
     *
     * @param int $int
     * @return ParagonIE_Sodium_Core32_Int64
     * @throws SodiumException
     * @throws TypeError
     */
    public function subInt($int)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($int, 'int', 1);
        $int = (int) $int;

        $return = new ParagonIE_Sodium_Core32_Int64();
        $return->unsignedInt = $this->unsignedInt;

        /** @var int $carry */
        $carry = 0;
        for ($i = 3; $i >= 0; --$i) {
            /** @var int $tmp */
            $tmp = $this->limbs[$i] - (($int >> 16) & 0xffff) + $carry;
            /** @var int $carry */
            $carry = $tmp >> 16;
            $return->limbs[$i] = (int) ($tmp & 0xffff);
        }
        return $return;
    }

    /**
     * The difference between two Int64 objects.
     *
     * @param ParagonIE_Sodium_Core32_Int64 $b
     * @return ParagonIE_Sodium_Core32_Int64
     */
    public function subInt64(ParagonIE_Sodium_Core32_Int64 $b)
    {
        $return = new ParagonIE_Sodium_Core32_Int64();
        $return->unsignedInt = $this->unsignedInt;
        /** @var int $carry */
        $carry = 0;
        for ($i = 3; $i >= 0; --$i) {
            /** @var int $tmp */
            $tmp = $this->limbs[$i] - $b->limbs[$i] + $carry;
            /** @var int $carry */
            $carry = ($tmp >> 16);
            $return->limbs[$i] = (int) ($tmp & 0xffff);
        }
        return $return;
    }

    /**
     * XOR this 64-bit integer with another.
     *
     * @param ParagonIE_Sodium_Core32_Int64 $b
     * @return ParagonIE_Sodium_Core32_Int64
     */
    public function xorInt64(ParagonIE_Sodium_Core32_Int64 $b)
    {
        $return = new ParagonIE_Sodium_Core32_Int64();
        $return->unsignedInt = $this->unsignedInt;
        $return->limbs = array(
            (int) ($this->limbs[0] ^ $b->limbs[0]),
            (int) ($this->limbs[1] ^ $b->limbs[1]),
            (int) ($this->limbs[2] ^ $b->limbs[2]),
            (int) ($this->limbs[3] ^ $b->limbs[3])
        );
        return $return;
    }

    /**
     * @param int $low
     * @param int $high
     * @return self
     * @throws SodiumException
     * @throws TypeError
     */
    public static function fromInts($low, $high)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($low, 'int', 1);
        ParagonIE_Sodium_Core32_Util::declareScalarType($high, 'int', 2);

        $high = (int) $high;
        $low = (int) $low;
        return new ParagonIE_Sodium_Core32_Int64(
            array(
                (int) (($high >> 16) & 0xffff),
                (int) ($high & 0xffff),
                (int) (($low >> 16) & 0xffff),
                (int) ($low & 0xffff)
            )
        );
    }

    /**
     * @param int $low
     * @return self
     * @throws SodiumException
     * @throws TypeError
     */
    public static function fromInt($low)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($low, 'int', 1);
        $low = (int) $low;

        return new ParagonIE_Sodium_Core32_Int64(
            array(
                0,
                0,
                (int) (($low >> 16) & 0xffff),
                (int) ($low & 0xffff)
            )
        );
    }

    /**
     * @return int
     */
    public function toInt()
    {
        return (int) (
            (($this->limbs[2] & 0xffff) << 16)
                |
            ($this->limbs[3] & 0xffff)
        );
    }

    /**
     * @param string $string
     * @return self
     * @throws SodiumException
     * @throws TypeError
     */
    public static function fromString($string)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($string, 'string', 1);
        $string = (string) $string;
        if (ParagonIE_Sodium_Core32_Util::strlen($string) !== 8) {
            throw new RangeException(
                'String must be 8 bytes; ' . ParagonIE_Sodium_Core32_Util::strlen($string) . ' given.'
            );
        }
        $return = new ParagonIE_Sodium_Core32_Int64();

        $return->limbs[0]  = (int) ((ParagonIE_Sodium_Core32_Util::chrToInt($string[0]) & 0xff) << 8);
        $return->limbs[0] |= (ParagonIE_Sodium_Core32_Util::chrToInt($string[1]) & 0xff);
        $return->limbs[1]  = (int) ((ParagonIE_Sodium_Core32_Util::chrToInt($string[2]) & 0xff) << 8);
        $return->limbs[1] |= (ParagonIE_Sodium_Core32_Util::chrToInt($string[3]) & 0xff);
        $return->limbs[2]  = (int) ((ParagonIE_Sodium_Core32_Util::chrToInt($string[4]) & 0xff) << 8);
        $return->limbs[2] |= (ParagonIE_Sodium_Core32_Util::chrToInt($string[5]) & 0xff);
        $return->limbs[3]  = (int) ((ParagonIE_Sodium_Core32_Util::chrToInt($string[6]) & 0xff) << 8);
        $return->limbs[3] |= (ParagonIE_Sodium_Core32_Util::chrToInt($string[7]) & 0xff);
        return $return;
    }

    /**
     * @param string $string
     * @return self
     * @throws SodiumException
     * @throws TypeError
     */
    public static function fromReverseString($string)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($string, 'string', 1);
        $string = (string) $string;
        if (ParagonIE_Sodium_Core32_Util::strlen($string) !== 8) {
            throw new RangeException(
                'String must be 8 bytes; ' . ParagonIE_Sodium_Core32_Util::strlen($string) . ' given.'
            );
        }
        $return = new ParagonIE_Sodium_Core32_Int64();

        $return->limbs[0]  = (int) ((ParagonIE_Sodium_Core32_Util::chrToInt($string[7]) & 0xff) << 8);
        $return->limbs[0] |= (ParagonIE_Sodium_Core32_Util::chrToInt($string[6]) & 0xff);
        $return->limbs[1]  = (int) ((ParagonIE_Sodium_Core32_Util::chrToInt($string[5]) & 0xff) << 8);
        $return->limbs[1] |= (ParagonIE_Sodium_Core32_Util::chrToInt($string[4]) & 0xff);
        $return->limbs[2]  = (int) ((ParagonIE_Sodium_Core32_Util::chrToInt($string[3]) & 0xff) << 8);
        $return->limbs[2] |= (ParagonIE_Sodium_Core32_Util::chrToInt($string[2]) & 0xff);
        $return->limbs[3]  = (int) ((ParagonIE_Sodium_Core32_Util::chrToInt($string[1]) & 0xff) << 8);
        $return->limbs[3] |= (ParagonIE_Sodium_Core32_Util::chrToInt($string[0]) & 0xff);
        return $return;
    }

    /**
     * @return array<int, int>
     */
    public function toArray()
    {
        return array(
            (int) ((($this->limbs[0] & 0xffff) << 16) | ($this->limbs[1] & 0xffff)),
            (int) ((($this->limbs[2] & 0xffff) << 16) | ($this->limbs[3] & 0xffff))
        );
    }

    /**
     * @return ParagonIE_Sodium_Core32_Int32
     */
    public function toInt32()
    {
        $return = new ParagonIE_Sodium_Core32_Int32();
        $return->limbs[0] = (int) ($this->limbs[2]);
        $return->limbs[1] = (int) ($this->limbs[3]);
        $return->unsignedInt = $this->unsignedInt;
        $return->overflow = (int) (ParagonIE_Sodium_Core32_Util::abs($this->limbs[1], 16) & 0xffff);
        return $return;
    }

    /**
     * @return ParagonIE_Sodium_Core32_Int64
     */
    public function toInt64()
    {
        $return = new ParagonIE_Sodium_Core32_Int64();
        $return->limbs[0] = (int) ($this->limbs[0]);
        $return->limbs[1] = (int) ($this->limbs[1]);
        $return->limbs[2] = (int) ($this->limbs[2]);
        $return->limbs[3] = (int) ($this->limbs[3]);
        $return->unsignedInt = $this->unsignedInt;
        $return->overflow = ParagonIE_Sodium_Core32_Util::abs($this->overflow);
        return $return;
    }

    /**
     * @param bool $bool
     * @return self
     */
    public function setUnsignedInt($bool = false)
    {
        $this->unsignedInt = !empty($bool);
        return $this;
    }

    /**
     * @return string
     * @throws TypeError
     */
    public function toString()
    {
        return ParagonIE_Sodium_Core32_Util::intToChr(($this->limbs[0] >> 8) & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr($this->limbs[0] & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr(($this->limbs[1] >> 8) & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr($this->limbs[1] & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr(($this->limbs[2] >> 8) & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr($this->limbs[2] & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr(($this->limbs[3] >> 8) & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr($this->limbs[3] & 0xff);
    }

    /**
     * @return string
     * @throws TypeError
     */
    public function toReverseString()
    {
        return ParagonIE_Sodium_Core32_Util::intToChr($this->limbs[3] & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr(($this->limbs[3] >> 8) & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr($this->limbs[2] & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr(($this->limbs[2] >> 8) & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr($this->limbs[1] & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr(($this->limbs[1] >> 8) & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr($this->limbs[0] & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr(($this->limbs[0] >> 8) & 0xff);
    }

    /**
     * @return string
     */
    public function __toString()
    {
        try {
            return $this->toString();
        } catch (TypeError $ex) {
            // PHP engine can't handle exceptions from __toString()
            return '';
        }
    }
}


<?php

if (class_exists('ParagonIE_Sodium_Core32_Poly1305', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core32_Poly1305
 */
abstract class ParagonIE_Sodium_Core32_Poly1305 extends ParagonIE_Sodium_Core32_Util
{
    const BLOCK_SIZE = 16;

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $m
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function onetimeauth($m, $key)
    {
        if (self::strlen($key) < 32) {
            throw new InvalidArgumentException(
                'Key must be 32 bytes long.'
            );
        }
        $state = new ParagonIE_Sodium_Core32_Poly1305_State(
            self::substr($key, 0, 32)
        );
        return $state
            ->update($m)
            ->finish();
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $mac
     * @param string $m
     * @param string $key
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    public static function onetimeauth_verify($mac, $m, $key)
    {
        if (self::strlen($key) < 32) {
            throw new InvalidArgumentException(
                'Key must be 32 bytes long.'
            );
        }
        $state = new ParagonIE_Sodium_Core32_Poly1305_State(
            self::substr($key, 0, 32)
        );
        $calc = $state
            ->update($m)
            ->finish();
        return self::verify_16($calc, $mac);
    }
}


<?php

if (class_exists('ParagonIE_Sodium_Core32_Salsa20', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core32_Salsa20
 */
abstract class ParagonIE_Sodium_Core32_Salsa20 extends ParagonIE_Sodium_Core32_Util
{
    const ROUNDS = 20;

    /**
     * Calculate an salsa20 hash of a single block
     *
     * @internal You should not use this directly from another application
     *
     * @param string $in
     * @param string $k
     * @param string|null $c
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function core_salsa20($in, $k, $c = null)
    {
        /**
         * @var ParagonIE_Sodium_Core32_Int32 $x0
         * @var ParagonIE_Sodium_Core32_Int32 $x1
         * @var ParagonIE_Sodium_Core32_Int32 $x2
         * @var ParagonIE_Sodium_Core32_Int32 $x3
         * @var ParagonIE_Sodium_Core32_Int32 $x4
         * @var ParagonIE_Sodium_Core32_Int32 $x5
         * @var ParagonIE_Sodium_Core32_Int32 $x6
         * @var ParagonIE_Sodium_Core32_Int32 $x7
         * @var ParagonIE_Sodium_Core32_Int32 $x8
         * @var ParagonIE_Sodium_Core32_Int32 $x9
         * @var ParagonIE_Sodium_Core32_Int32 $x10
         * @var ParagonIE_Sodium_Core32_Int32 $x11
         * @var ParagonIE_Sodium_Core32_Int32 $x12
         * @var ParagonIE_Sodium_Core32_Int32 $x13
         * @var ParagonIE_Sodium_Core32_Int32 $x14
         * @var ParagonIE_Sodium_Core32_Int32 $x15
         * @var ParagonIE_Sodium_Core32_Int32 $j0
         * @var ParagonIE_Sodium_Core32_Int32 $j1
         * @var ParagonIE_Sodium_Core32_Int32 $j2
         * @var ParagonIE_Sodium_Core32_Int32 $j3
         * @var ParagonIE_Sodium_Core32_Int32 $j4
         * @var ParagonIE_Sodium_Core32_Int32 $j5
         * @var ParagonIE_Sodium_Core32_Int32 $j6
         * @var ParagonIE_Sodium_Core32_Int32 $j7
         * @var ParagonIE_Sodium_Core32_Int32 $j8
         * @var ParagonIE_Sodium_Core32_Int32 $j9
         * @var ParagonIE_Sodium_Core32_Int32 $j10
         * @var ParagonIE_Sodium_Core32_Int32 $j11
         * @var ParagonIE_Sodium_Core32_Int32 $j12
         * @var ParagonIE_Sodium_Core32_Int32 $j13
         * @var ParagonIE_Sodium_Core32_Int32 $j14
         * @var ParagonIE_Sodium_Core32_Int32 $j15
         */
        if (self::strlen($k) < 32) {
            throw new RangeException('Key must be 32 bytes long');
        }
        if ($c === null) {
            $x0  = new ParagonIE_Sodium_Core32_Int32(array(0x6170, 0x7865));
            $x5  = new ParagonIE_Sodium_Core32_Int32(array(0x3320, 0x646e));
            $x10 = new ParagonIE_Sodium_Core32_Int32(array(0x7962, 0x2d32));
            $x15 = new ParagonIE_Sodium_Core32_Int32(array(0x6b20, 0x6574));
        } else {
            $x0  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($c, 0, 4));
            $x5  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($c, 4, 4));
            $x10 = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($c, 8, 4));
            $x15 = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($c, 12, 4));
        }
        $x1  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 0, 4));
        $x2  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 4, 4));
        $x3  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 8, 4));
        $x4  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 12, 4));
        $x6  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($in, 0, 4));
        $x7  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($in, 4, 4));
        $x8  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($in, 8, 4));
        $x9  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($in, 12, 4));
        $x11 = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 16, 4));
        $x12 = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 20, 4));
        $x13 = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 24, 4));
        $x14 = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 28, 4));

        $j0  = clone $x0;
        $j1  = clone $x1;
        $j2  = clone $x2;
        $j3  = clone $x3;
        $j4  = clone $x4;
        $j5  = clone $x5;
        $j6  = clone $x6;
        $j7  = clone $x7;
        $j8  = clone $x8;
        $j9  = clone $x9;
        $j10  = clone $x10;
        $j11  = clone $x11;
        $j12  = clone $x12;
        $j13  = clone $x13;
        $j14  = clone $x14;
        $j15  = clone $x15;

        for ($i = self::ROUNDS; $i > 0; $i -= 2) {
            $x4  = $x4->xorInt32($x0->addInt32($x12)->rotateLeft(7));
            $x8  = $x8->xorInt32($x4->addInt32($x0)->rotateLeft(9));
            $x12 = $x12->xorInt32($x8->addInt32($x4)->rotateLeft(13));
            $x0  = $x0->xorInt32($x12->addInt32($x8)->rotateLeft(18));

            $x9  = $x9->xorInt32($x5->addInt32($x1)->rotateLeft(7));
            $x13 = $x13->xorInt32($x9->addInt32($x5)->rotateLeft(9));
            $x1  = $x1->xorInt32($x13->addInt32($x9)->rotateLeft(13));
            $x5  = $x5->xorInt32($x1->addInt32($x13)->rotateLeft(18));

            $x14 = $x14->xorInt32($x10->addInt32($x6)->rotateLeft(7));
            $x2  = $x2->xorInt32($x14->addInt32($x10)->rotateLeft(9));
            $x6  = $x6->xorInt32($x2->addInt32($x14)->rotateLeft(13));
            $x10 = $x10->xorInt32($x6->addInt32($x2)->rotateLeft(18));

            $x3  = $x3->xorInt32($x15->addInt32($x11)->rotateLeft(7));
            $x7  = $x7->xorInt32($x3->addInt32($x15)->rotateLeft(9));
            $x11 = $x11->xorInt32($x7->addInt32($x3)->rotateLeft(13));
            $x15 = $x15->xorInt32($x11->addInt32($x7)->rotateLeft(18));

            $x1  = $x1->xorInt32($x0->addInt32($x3)->rotateLeft(7));
            $x2  = $x2->xorInt32($x1->addInt32($x0)->rotateLeft(9));
            $x3  = $x3->xorInt32($x2->addInt32($x1)->rotateLeft(13));
            $x0  = $x0->xorInt32($x3->addInt32($x2)->rotateLeft(18));

            $x6  = $x6->xorInt32($x5->addInt32($x4)->rotateLeft(7));
            $x7  = $x7->xorInt32($x6->addInt32($x5)->rotateLeft(9));
            $x4  = $x4->xorInt32($x7->addInt32($x6)->rotateLeft(13));
            $x5  = $x5->xorInt32($x4->addInt32($x7)->rotateLeft(18));

            $x11 = $x11->xorInt32($x10->addInt32($x9)->rotateLeft(7));
            $x8  = $x8->xorInt32($x11->addInt32($x10)->rotateLeft(9));
            $x9  = $x9->xorInt32($x8->addInt32($x11)->rotateLeft(13));
            $x10 = $x10->xorInt32($x9->addInt32($x8)->rotateLeft(18));

            $x12 = $x12->xorInt32($x15->addInt32($x14)->rotateLeft(7));
            $x13 = $x13->xorInt32($x12->addInt32($x15)->rotateLeft(9));
            $x14 = $x14->xorInt32($x13->addInt32($x12)->rotateLeft(13));
            $x15 = $x15->xorInt32($x14->addInt32($x13)->rotateLeft(18));
        }

        $x0  = $x0->addInt32($j0);
        $x1  = $x1->addInt32($j1);
        $x2  = $x2->addInt32($j2);
        $x3  = $x3->addInt32($j3);
        $x4  = $x4->addInt32($j4);
        $x5  = $x5->addInt32($j5);
        $x6  = $x6->addInt32($j6);
        $x7  = $x7->addInt32($j7);
        $x8  = $x8->addInt32($j8);
        $x9  = $x9->addInt32($j9);
        $x10 = $x10->addInt32($j10);
        $x11 = $x11->addInt32($j11);
        $x12 = $x12->addInt32($j12);
        $x13 = $x13->addInt32($j13);
        $x14 = $x14->addInt32($j14);
        $x15 = $x15->addInt32($j15);

        return $x0->toReverseString() .
            $x1->toReverseString() .
            $x2->toReverseString() .
            $x3->toReverseString() .
            $x4->toReverseString() .
            $x5->toReverseString() .
            $x6->toReverseString() .
            $x7->toReverseString() .
            $x8->toReverseString() .
            $x9->toReverseString() .
            $x10->toReverseString() .
            $x11->toReverseString() .
            $x12->toReverseString() .
            $x13->toReverseString() .
            $x14->toReverseString() .
            $x15->toReverseString();
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param int $len
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function salsa20($len, $nonce, $key)
    {
        if (self::strlen($key) !== 32) {
            throw new RangeException('Key must be 32 bytes long');
        }
        $kcopy = '' . $key;
        $in = self::substr($nonce, 0, 8) . str_repeat("\0", 8);
        $c = '';
        while ($len >= 64) {
            $c .= self::core_salsa20($in, $kcopy, null);
            $u = 1;
            // Internal counter.
            for ($i = 8; $i < 16; ++$i) {
                $u += self::chrToInt($in[$i]);
                $in[$i] = self::intToChr($u & 0xff);
                $u >>= 8;
            }
            $len -= 64;
        }
        if ($len > 0) {
            $c .= self::substr(
                self::core_salsa20($in, $kcopy, null),
                0,
                $len
            );
        }
        try {
            ParagonIE_Sodium_Compat::memzero($kcopy);
        } catch (SodiumException $ex) {
            $kcopy = null;
        }
        return $c;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $m
     * @param string $n
     * @param int $ic
     * @param string $k
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function salsa20_xor_ic($m, $n, $ic, $k)
    {
        $mlen = self::strlen($m);
        if ($mlen < 1) {
            return '';
        }
        $kcopy = self::substr($k, 0, 32);
        $in = self::substr($n, 0, 8);
        // Initialize the counter
        $in .= ParagonIE_Sodium_Core32_Util::store64_le($ic);

        $c = '';
        while ($mlen >= 64) {
            $block = self::core_salsa20($in, $kcopy, null);
            $c .= self::xorStrings(
                self::substr($m, 0, 64),
                self::substr($block, 0, 64)
            );
            $u = 1;
            for ($i = 8; $i < 16; ++$i) {
                $u += self::chrToInt($in[$i]);
                $in[$i] = self::intToChr($u & 0xff);
                $u >>= 8;
            }

            $mlen -= 64;
            $m = self::substr($m, 64);
        }

        if ($mlen) {
            $block = self::core_salsa20($in, $kcopy, null);
            $c .= self::xorStrings(
                self::substr($m, 0, $mlen),
                self::substr($block, 0, $mlen)
            );
        }
        try {
            ParagonIE_Sodium_Compat::memzero($block);
            ParagonIE_Sodium_Compat::memzero($kcopy);
        } catch (SodiumException $ex) {
            $block = null;
            $kcopy = null;
        }

        return $c;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $message
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function salsa20_xor($message, $nonce, $key)
    {
        return self::xorStrings(
            $message,
            self::salsa20(
                self::strlen($message),
                $nonce,
                $key
            )
        );
    }
}