wp/wp-includes/sodium_compat/src/Crypto32.php
changeset 9 177826044cd9
child 16 a86126ab1dd4
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wp/wp-includes/sodium_compat/src/Crypto32.php	Mon Oct 14 18:28:13 2019 +0200
@@ -0,0 +1,1251 @@
+<?php
+
+if (class_exists('ParagonIE_Sodium_Crypto32', false)) {
+    return;
+}
+
+/**
+ * Class ParagonIE_Sodium_Crypto
+ *
+ * ATTENTION!
+ *
+ * If you are using this library, you should be using
+ * ParagonIE_Sodium_Compat in your code, not this class.
+ */
+abstract class ParagonIE_Sodium_Crypto32
+{
+    const aead_chacha20poly1305_KEYBYTES = 32;
+    const aead_chacha20poly1305_NSECBYTES = 0;
+    const aead_chacha20poly1305_NPUBBYTES = 8;
+    const aead_chacha20poly1305_ABYTES = 16;
+
+    const aead_chacha20poly1305_IETF_KEYBYTES = 32;
+    const aead_chacha20poly1305_IETF_NSECBYTES = 0;
+    const aead_chacha20poly1305_IETF_NPUBBYTES = 12;
+    const aead_chacha20poly1305_IETF_ABYTES = 16;
+
+    const aead_xchacha20poly1305_IETF_KEYBYTES = 32;
+    const aead_xchacha20poly1305_IETF_NSECBYTES = 0;
+    const aead_xchacha20poly1305_IETF_NPUBBYTES = 24;
+    const aead_xchacha20poly1305_IETF_ABYTES = 16;
+
+    const box_curve25519xsalsa20poly1305_SEEDBYTES = 32;
+    const box_curve25519xsalsa20poly1305_PUBLICKEYBYTES = 32;
+    const box_curve25519xsalsa20poly1305_SECRETKEYBYTES = 32;
+    const box_curve25519xsalsa20poly1305_BEFORENMBYTES = 32;
+    const box_curve25519xsalsa20poly1305_NONCEBYTES = 24;
+    const box_curve25519xsalsa20poly1305_MACBYTES = 16;
+    const box_curve25519xsalsa20poly1305_BOXZEROBYTES = 16;
+    const box_curve25519xsalsa20poly1305_ZEROBYTES = 32;
+
+    const onetimeauth_poly1305_BYTES = 16;
+    const onetimeauth_poly1305_KEYBYTES = 32;
+
+    const secretbox_xsalsa20poly1305_KEYBYTES = 32;
+    const secretbox_xsalsa20poly1305_NONCEBYTES = 24;
+    const secretbox_xsalsa20poly1305_MACBYTES = 16;
+    const secretbox_xsalsa20poly1305_BOXZEROBYTES = 16;
+    const secretbox_xsalsa20poly1305_ZEROBYTES = 32;
+
+    const secretbox_xchacha20poly1305_KEYBYTES = 32;
+    const secretbox_xchacha20poly1305_NONCEBYTES = 24;
+    const secretbox_xchacha20poly1305_MACBYTES = 16;
+    const secretbox_xchacha20poly1305_BOXZEROBYTES = 16;
+    const secretbox_xchacha20poly1305_ZEROBYTES = 32;
+
+    const stream_salsa20_KEYBYTES = 32;
+
+    /**
+     * AEAD Decryption with ChaCha20-Poly1305
+     *
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $message
+     * @param string $ad
+     * @param string $nonce
+     * @param string $key
+     * @return string
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function aead_chacha20poly1305_decrypt(
+        $message = '',
+        $ad = '',
+        $nonce = '',
+        $key = ''
+    ) {
+        /** @var int $len - Length of message (ciphertext + MAC) */
+        $len = ParagonIE_Sodium_Core32_Util::strlen($message);
+
+        /** @var int  $clen - Length of ciphertext */
+        $clen = $len - self::aead_chacha20poly1305_ABYTES;
+
+        /** @var int $adlen - Length of associated data */
+        $adlen = ParagonIE_Sodium_Core32_Util::strlen($ad);
+
+        /** @var string $mac - Message authentication code */
+        $mac = ParagonIE_Sodium_Core32_Util::substr(
+            $message,
+            $clen,
+            self::aead_chacha20poly1305_ABYTES
+        );
+
+        /** @var string $ciphertext - The encrypted message (sans MAC) */
+        $ciphertext = ParagonIE_Sodium_Core32_Util::substr($message, 0, $clen);
+
+        /** @var string The first block of the chacha20 keystream, used as a poly1305 key */
+        $block0 = ParagonIE_Sodium_Core32_ChaCha20::stream(
+            32,
+            $nonce,
+            $key
+        );
+
+        /* Recalculate the Poly1305 authentication tag (MAC): */
+        $state = new ParagonIE_Sodium_Core32_Poly1305_State($block0);
+        try {
+            ParagonIE_Sodium_Compat::memzero($block0);
+        } catch (SodiumException $ex) {
+            $block0 = null;
+        }
+        $state->update($ad);
+        $state->update(ParagonIE_Sodium_Core32_Util::store64_le($adlen));
+        $state->update($ciphertext);
+        $state->update(ParagonIE_Sodium_Core32_Util::store64_le($clen));
+        $computed_mac = $state->finish();
+
+        /* Compare the given MAC with the recalculated MAC: */
+        if (!ParagonIE_Sodium_Core32_Util::verify_16($computed_mac, $mac)) {
+            throw new SodiumException('Invalid MAC');
+        }
+
+        // Here, we know that the MAC is valid, so we decrypt and return the plaintext
+        return ParagonIE_Sodium_Core32_ChaCha20::streamXorIc(
+            $ciphertext,
+            $nonce,
+            $key,
+            ParagonIE_Sodium_Core32_Util::store64_le(1)
+        );
+    }
+
+    /**
+     * AEAD Encryption with ChaCha20-Poly1305
+     *
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $message
+     * @param string $ad
+     * @param string $nonce
+     * @param string $key
+     * @return string
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function aead_chacha20poly1305_encrypt(
+        $message = '',
+        $ad = '',
+        $nonce = '',
+        $key = ''
+    ) {
+        /** @var int $len - Length of the plaintext message */
+        $len = ParagonIE_Sodium_Core32_Util::strlen($message);
+
+        /** @var int $adlen - Length of the associated data */
+        $adlen = ParagonIE_Sodium_Core32_Util::strlen($ad);
+
+        /** @var string The first block of the chacha20 keystream, used as a poly1305 key */
+        $block0 = ParagonIE_Sodium_Core32_ChaCha20::stream(
+            32,
+            $nonce,
+            $key
+        );
+        $state = new ParagonIE_Sodium_Core32_Poly1305_State($block0);
+        try {
+            ParagonIE_Sodium_Compat::memzero($block0);
+        } catch (SodiumException $ex) {
+            $block0 = null;
+        }
+
+        /** @var string $ciphertext - Raw encrypted data */
+        $ciphertext = ParagonIE_Sodium_Core32_ChaCha20::streamXorIc(
+            $message,
+            $nonce,
+            $key,
+            ParagonIE_Sodium_Core32_Util::store64_le(1)
+        );
+
+        $state->update($ad);
+        $state->update(ParagonIE_Sodium_Core32_Util::store64_le($adlen));
+        $state->update($ciphertext);
+        $state->update(ParagonIE_Sodium_Core32_Util::store64_le($len));
+        return $ciphertext . $state->finish();
+    }
+
+    /**
+     * AEAD Decryption with ChaCha20-Poly1305, IETF mode (96-bit nonce)
+     *
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $message
+     * @param string $ad
+     * @param string $nonce
+     * @param string $key
+     * @return string
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function aead_chacha20poly1305_ietf_decrypt(
+        $message = '',
+        $ad = '',
+        $nonce = '',
+        $key = ''
+    ) {
+        /** @var int $adlen - Length of associated data */
+        $adlen = ParagonIE_Sodium_Core32_Util::strlen($ad);
+
+        /** @var int $len - Length of message (ciphertext + MAC) */
+        $len = ParagonIE_Sodium_Core32_Util::strlen($message);
+
+        /** @var int  $clen - Length of ciphertext */
+        $clen = $len - self::aead_chacha20poly1305_IETF_ABYTES;
+
+        /** @var string The first block of the chacha20 keystream, used as a poly1305 key */
+        $block0 = ParagonIE_Sodium_Core32_ChaCha20::ietfStream(
+            32,
+            $nonce,
+            $key
+        );
+
+        /** @var string $mac - Message authentication code */
+        $mac = ParagonIE_Sodium_Core32_Util::substr(
+            $message,
+            $len - self::aead_chacha20poly1305_IETF_ABYTES,
+            self::aead_chacha20poly1305_IETF_ABYTES
+        );
+
+        /** @var string $ciphertext - The encrypted message (sans MAC) */
+        $ciphertext = ParagonIE_Sodium_Core32_Util::substr(
+            $message,
+            0,
+            $len - self::aead_chacha20poly1305_IETF_ABYTES
+        );
+
+        /* Recalculate the Poly1305 authentication tag (MAC): */
+        $state = new ParagonIE_Sodium_Core32_Poly1305_State($block0);
+        try {
+            ParagonIE_Sodium_Compat::memzero($block0);
+        } catch (SodiumException $ex) {
+            $block0 = null;
+        }
+        $state->update($ad);
+        $state->update(str_repeat("\x00", ((0x10 - $adlen) & 0xf)));
+        $state->update($ciphertext);
+        $state->update(str_repeat("\x00", (0x10 - $clen) & 0xf));
+        $state->update(ParagonIE_Sodium_Core32_Util::store64_le($adlen));
+        $state->update(ParagonIE_Sodium_Core32_Util::store64_le($clen));
+        $computed_mac = $state->finish();
+
+        /* Compare the given MAC with the recalculated MAC: */
+        if (!ParagonIE_Sodium_Core32_Util::verify_16($computed_mac, $mac)) {
+            throw new SodiumException('Invalid MAC');
+        }
+
+        // Here, we know that the MAC is valid, so we decrypt and return the plaintext
+        return ParagonIE_Sodium_Core32_ChaCha20::ietfStreamXorIc(
+            $ciphertext,
+            $nonce,
+            $key,
+            ParagonIE_Sodium_Core32_Util::store64_le(1)
+        );
+    }
+
+    /**
+     * AEAD Encryption with ChaCha20-Poly1305, IETF mode (96-bit nonce)
+     *
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $message
+     * @param string $ad
+     * @param string $nonce
+     * @param string $key
+     * @return string
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function aead_chacha20poly1305_ietf_encrypt(
+        $message = '',
+        $ad = '',
+        $nonce = '',
+        $key = ''
+    ) {
+        /** @var int $len - Length of the plaintext message */
+        $len = ParagonIE_Sodium_Core32_Util::strlen($message);
+
+        /** @var int $adlen - Length of the associated data */
+        $adlen = ParagonIE_Sodium_Core32_Util::strlen($ad);
+
+        /** @var string The first block of the chacha20 keystream, used as a poly1305 key */
+        $block0 = ParagonIE_Sodium_Core32_ChaCha20::ietfStream(
+            32,
+            $nonce,
+            $key
+        );
+        $state = new ParagonIE_Sodium_Core32_Poly1305_State($block0);
+        try {
+            ParagonIE_Sodium_Compat::memzero($block0);
+        } catch (SodiumException $ex) {
+            $block0 = null;
+        }
+
+        /** @var string $ciphertext - Raw encrypted data */
+        $ciphertext = ParagonIE_Sodium_Core32_ChaCha20::ietfStreamXorIc(
+            $message,
+            $nonce,
+            $key,
+            ParagonIE_Sodium_Core32_Util::store64_le(1)
+        );
+
+        $state->update($ad);
+        $state->update(str_repeat("\x00", ((0x10 - $adlen) & 0xf)));
+        $state->update($ciphertext);
+        $state->update(str_repeat("\x00", ((0x10 - $len) & 0xf)));
+        $state->update(ParagonIE_Sodium_Core32_Util::store64_le($adlen));
+        $state->update(ParagonIE_Sodium_Core32_Util::store64_le($len));
+        return $ciphertext . $state->finish();
+    }
+
+    /**
+     * AEAD Decryption with ChaCha20-Poly1305, IETF mode (96-bit nonce)
+     *
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $message
+     * @param string $ad
+     * @param string $nonce
+     * @param string $key
+     * @return string
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function aead_xchacha20poly1305_ietf_decrypt(
+        $message = '',
+        $ad = '',
+        $nonce = '',
+        $key = ''
+    ) {
+        $subkey = ParagonIE_Sodium_Core32_HChaCha20::hChaCha20(
+            ParagonIE_Sodium_Core32_Util::substr($nonce, 0, 16),
+            $key
+        );
+        $nonceLast = "\x00\x00\x00\x00" .
+            ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8);
+
+        return self::aead_chacha20poly1305_ietf_decrypt($message, $ad, $nonceLast, $subkey);
+    }
+
+    /**
+     * AEAD Encryption with ChaCha20-Poly1305, IETF mode (96-bit nonce)
+     *
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $message
+     * @param string $ad
+     * @param string $nonce
+     * @param string $key
+     * @return string
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function aead_xchacha20poly1305_ietf_encrypt(
+        $message = '',
+        $ad = '',
+        $nonce = '',
+        $key = ''
+    ) {
+        $subkey = ParagonIE_Sodium_Core32_HChaCha20::hChaCha20(
+            ParagonIE_Sodium_Core32_Util::substr($nonce, 0, 16),
+            $key
+        );
+        $nonceLast = "\x00\x00\x00\x00" .
+            ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8);
+
+        return self::aead_chacha20poly1305_ietf_encrypt($message, $ad, $nonceLast, $subkey);
+    }
+
+    /**
+     * HMAC-SHA-512-256 (a.k.a. the leftmost 256 bits of HMAC-SHA-512)
+     *
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $message
+     * @param string $key
+     * @return string
+     * @throws TypeError
+     */
+    public static function auth($message, $key)
+    {
+        return ParagonIE_Sodium_Core32_Util::substr(
+            hash_hmac('sha512', $message, $key, true),
+            0,
+            32
+        );
+    }
+
+    /**
+     * HMAC-SHA-512-256 validation. Constant-time via hash_equals().
+     *
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $mac
+     * @param string $message
+     * @param string $key
+     * @return bool
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function auth_verify($mac, $message, $key)
+    {
+        return ParagonIE_Sodium_Core32_Util::hashEquals(
+            $mac,
+            self::auth($message, $key)
+        );
+    }
+
+    /**
+     * X25519 key exchange followed by XSalsa20Poly1305 symmetric encryption
+     *
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $plaintext
+     * @param string $nonce
+     * @param string $keypair
+     * @return string
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function box($plaintext, $nonce, $keypair)
+    {
+        return self::secretbox(
+            $plaintext,
+            $nonce,
+            self::box_beforenm(
+                self::box_secretkey($keypair),
+                self::box_publickey($keypair)
+            )
+        );
+    }
+
+    /**
+     * X25519-XSalsa20-Poly1305 with one ephemeral X25519 keypair.
+     *
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $message
+     * @param string $publicKey
+     * @return string
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function box_seal($message, $publicKey)
+    {
+        /** @var string $ephemeralKeypair */
+        $ephemeralKeypair = self::box_keypair();
+
+        /** @var string $ephemeralSK */
+        $ephemeralSK = self::box_secretkey($ephemeralKeypair);
+
+        /** @var string $ephemeralPK */
+        $ephemeralPK = self::box_publickey($ephemeralKeypair);
+
+        /** @var string $nonce */
+        $nonce = self::generichash(
+            $ephemeralPK . $publicKey,
+            '',
+            24
+        );
+
+        /** @var string $keypair - The combined keypair used in crypto_box() */
+        $keypair = self::box_keypair_from_secretkey_and_publickey($ephemeralSK, $publicKey);
+
+        /** @var string $ciphertext Ciphertext + MAC from crypto_box */
+        $ciphertext = self::box($message, $nonce, $keypair);
+        try {
+            ParagonIE_Sodium_Compat::memzero($ephemeralKeypair);
+            ParagonIE_Sodium_Compat::memzero($ephemeralSK);
+            ParagonIE_Sodium_Compat::memzero($nonce);
+        } catch (SodiumException $ex) {
+            $ephemeralKeypair = null;
+            $ephemeralSK = null;
+            $nonce = null;
+        }
+        return $ephemeralPK . $ciphertext;
+    }
+
+    /**
+     * Opens a message encrypted via box_seal().
+     *
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $message
+     * @param string $keypair
+     * @return string
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function box_seal_open($message, $keypair)
+    {
+        /** @var string $ephemeralPK */
+        $ephemeralPK = ParagonIE_Sodium_Core32_Util::substr($message, 0, 32);
+
+        /** @var string $ciphertext (ciphertext + MAC) */
+        $ciphertext = ParagonIE_Sodium_Core32_Util::substr($message, 32);
+
+        /** @var string $secretKey */
+        $secretKey = self::box_secretkey($keypair);
+
+        /** @var string $publicKey */
+        $publicKey = self::box_publickey($keypair);
+
+        /** @var string $nonce */
+        $nonce = self::generichash(
+            $ephemeralPK . $publicKey,
+            '',
+            24
+        );
+
+        /** @var string $keypair */
+        $keypair = self::box_keypair_from_secretkey_and_publickey($secretKey, $ephemeralPK);
+
+        /** @var string $m */
+        $m = self::box_open($ciphertext, $nonce, $keypair);
+        try {
+            ParagonIE_Sodium_Compat::memzero($secretKey);
+            ParagonIE_Sodium_Compat::memzero($ephemeralPK);
+            ParagonIE_Sodium_Compat::memzero($nonce);
+        } catch (SodiumException $ex) {
+            $secretKey = null;
+            $ephemeralPK = null;
+            $nonce = null;
+        }
+        return $m;
+    }
+
+    /**
+     * Used by crypto_box() to get the crypto_secretbox() key.
+     *
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $sk
+     * @param string $pk
+     * @return string
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function box_beforenm($sk, $pk)
+    {
+        return ParagonIE_Sodium_Core32_HSalsa20::hsalsa20(
+            str_repeat("\x00", 16),
+            self::scalarmult($sk, $pk)
+        );
+    }
+
+    /**
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @return string
+     * @throws Exception
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function box_keypair()
+    {
+        $sKey = random_bytes(32);
+        $pKey = self::scalarmult_base($sKey);
+        return $sKey . $pKey;
+    }
+
+    /**
+     * @param string $seed
+     * @return string
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function box_seed_keypair($seed)
+    {
+        $sKey = ParagonIE_Sodium_Core32_Util::substr(
+            hash('sha512', $seed, true),
+            0,
+            32
+        );
+        $pKey = self::scalarmult_base($sKey);
+        return $sKey . $pKey;
+    }
+
+    /**
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $sKey
+     * @param string $pKey
+     * @return string
+     * @throws TypeError
+     */
+    public static function box_keypair_from_secretkey_and_publickey($sKey, $pKey)
+    {
+        return ParagonIE_Sodium_Core32_Util::substr($sKey, 0, 32) .
+            ParagonIE_Sodium_Core32_Util::substr($pKey, 0, 32);
+    }
+
+    /**
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $keypair
+     * @return string
+     * @throws RangeException
+     * @throws TypeError
+     */
+    public static function box_secretkey($keypair)
+    {
+        if (ParagonIE_Sodium_Core32_Util::strlen($keypair) !== 64) {
+            throw new RangeException(
+                'Must be ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES bytes long.'
+            );
+        }
+        return ParagonIE_Sodium_Core32_Util::substr($keypair, 0, 32);
+    }
+
+    /**
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $keypair
+     * @return string
+     * @throws RangeException
+     * @throws TypeError
+     */
+    public static function box_publickey($keypair)
+    {
+        if (ParagonIE_Sodium_Core32_Util::strlen($keypair) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES) {
+            throw new RangeException(
+                'Must be ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES bytes long.'
+            );
+        }
+        return ParagonIE_Sodium_Core32_Util::substr($keypair, 32, 32);
+    }
+
+    /**
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $sKey
+     * @return string
+     * @throws RangeException
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function box_publickey_from_secretkey($sKey)
+    {
+        if (ParagonIE_Sodium_Core32_Util::strlen($sKey) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_SECRETKEYBYTES) {
+            throw new RangeException(
+                'Must be ParagonIE_Sodium_Compat::CRYPTO_BOX_SECRETKEYBYTES bytes long.'
+            );
+        }
+        return self::scalarmult_base($sKey);
+    }
+
+    /**
+     * Decrypt a message encrypted with box().
+     *
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $ciphertext
+     * @param string $nonce
+     * @param string $keypair
+     * @return string
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function box_open($ciphertext, $nonce, $keypair)
+    {
+        return self::secretbox_open(
+            $ciphertext,
+            $nonce,
+            self::box_beforenm(
+                self::box_secretkey($keypair),
+                self::box_publickey($keypair)
+            )
+        );
+    }
+
+    /**
+     * Calculate a BLAKE2b hash.
+     *
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $message
+     * @param string|null $key
+     * @param int $outlen
+     * @return string
+     * @throws RangeException
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function generichash($message, $key = '', $outlen = 32)
+    {
+        // This ensures that ParagonIE_Sodium_Core32_BLAKE2b::$iv is initialized
+        ParagonIE_Sodium_Core32_BLAKE2b::pseudoConstructor();
+
+        $k = null;
+        if (!empty($key)) {
+            /** @var SplFixedArray $k */
+            $k = ParagonIE_Sodium_Core32_BLAKE2b::stringToSplFixedArray($key);
+            if ($k->count() > ParagonIE_Sodium_Core32_BLAKE2b::KEYBYTES) {
+                throw new RangeException('Invalid key size');
+            }
+        }
+
+        /** @var SplFixedArray $in */
+        $in = ParagonIE_Sodium_Core32_BLAKE2b::stringToSplFixedArray($message);
+
+        /** @var SplFixedArray $ctx */
+        $ctx = ParagonIE_Sodium_Core32_BLAKE2b::init($k, $outlen);
+        ParagonIE_Sodium_Core32_BLAKE2b::update($ctx, $in, $in->count());
+
+        /** @var SplFixedArray $out */
+        $out = new SplFixedArray($outlen);
+        $out = ParagonIE_Sodium_Core32_BLAKE2b::finish($ctx, $out);
+
+        /** @var array<int, int> */
+        $outArray = $out->toArray();
+        return ParagonIE_Sodium_Core32_Util::intArrayToString($outArray);
+    }
+
+    /**
+     * Finalize a BLAKE2b hashing context, returning the hash.
+     *
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $ctx
+     * @param int $outlen
+     * @return string
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function generichash_final($ctx, $outlen = 32)
+    {
+        if (!is_string($ctx)) {
+            throw new TypeError('Context must be a string');
+        }
+        $out = new SplFixedArray($outlen);
+
+        /** @var SplFixedArray $context */
+        $context = ParagonIE_Sodium_Core32_BLAKE2b::stringToContext($ctx);
+
+        /** @var SplFixedArray $out */
+        $out = ParagonIE_Sodium_Core32_BLAKE2b::finish($context, $out);
+
+        /** @var array<int, int> */
+        $outArray = $out->toArray();
+        return ParagonIE_Sodium_Core32_Util::intArrayToString($outArray);
+    }
+
+    /**
+     * Initialize a hashing context for BLAKE2b.
+     *
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $key
+     * @param int $outputLength
+     * @return string
+     * @throws RangeException
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function generichash_init($key = '', $outputLength = 32)
+    {
+        // This ensures that ParagonIE_Sodium_Core32_BLAKE2b::$iv is initialized
+        ParagonIE_Sodium_Core32_BLAKE2b::pseudoConstructor();
+
+        $k = null;
+        if (!empty($key)) {
+            $k = ParagonIE_Sodium_Core32_BLAKE2b::stringToSplFixedArray($key);
+            if ($k->count() > ParagonIE_Sodium_Core32_BLAKE2b::KEYBYTES) {
+                throw new RangeException('Invalid key size');
+            }
+        }
+
+        /** @var SplFixedArray $ctx */
+        $ctx = ParagonIE_Sodium_Core32_BLAKE2b::init($k, $outputLength);
+
+        return ParagonIE_Sodium_Core32_BLAKE2b::contextToString($ctx);
+    }
+
+    /**
+     * Update a hashing context for BLAKE2b with $message
+     *
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $ctx
+     * @param string $message
+     * @return string
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function generichash_update($ctx, $message)
+    {
+        // This ensures that ParagonIE_Sodium_Core32_BLAKE2b::$iv is initialized
+        ParagonIE_Sodium_Core32_BLAKE2b::pseudoConstructor();
+
+        /** @var SplFixedArray $context */
+        $context = ParagonIE_Sodium_Core32_BLAKE2b::stringToContext($ctx);
+
+        /** @var SplFixedArray $in */
+        $in = ParagonIE_Sodium_Core32_BLAKE2b::stringToSplFixedArray($message);
+
+        ParagonIE_Sodium_Core32_BLAKE2b::update($context, $in, $in->count());
+
+        return ParagonIE_Sodium_Core32_BLAKE2b::contextToString($context);
+    }
+
+    /**
+     * Libsodium's crypto_kx().
+     *
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $my_sk
+     * @param string $their_pk
+     * @param string $client_pk
+     * @param string $server_pk
+     * @return string
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function keyExchange($my_sk, $their_pk, $client_pk, $server_pk)
+    {
+        return self::generichash(
+            self::scalarmult($my_sk, $their_pk) .
+            $client_pk .
+            $server_pk
+        );
+    }
+
+    /**
+     * ECDH over Curve25519
+     *
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $sKey
+     * @param string $pKey
+     * @return string
+     *
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function scalarmult($sKey, $pKey)
+    {
+        $q = ParagonIE_Sodium_Core32_X25519::crypto_scalarmult_curve25519_ref10($sKey, $pKey);
+        self::scalarmult_throw_if_zero($q);
+        return $q;
+    }
+
+    /**
+     * ECDH over Curve25519, using the basepoint.
+     * Used to get a secret key from a public key.
+     *
+     * @param string $secret
+     * @return string
+     *
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function scalarmult_base($secret)
+    {
+        $q = ParagonIE_Sodium_Core32_X25519::crypto_scalarmult_curve25519_ref10_base($secret);
+        self::scalarmult_throw_if_zero($q);
+        return $q;
+    }
+
+    /**
+     * This throws an Error if a zero public key was passed to the function.
+     *
+     * @param string $q
+     * @return void
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    protected static function scalarmult_throw_if_zero($q)
+    {
+        $d = 0;
+        for ($i = 0; $i < self::box_curve25519xsalsa20poly1305_SECRETKEYBYTES; ++$i) {
+            $d |= ParagonIE_Sodium_Core32_Util::chrToInt($q[$i]);
+        }
+
+        /* branch-free variant of === 0 */
+        if (-(1 & (($d - 1) >> 8))) {
+            throw new SodiumException('Zero public key is not allowed');
+        }
+    }
+
+    /**
+     * XSalsa20-Poly1305 authenticated symmetric-key encryption.
+     *
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $plaintext
+     * @param string $nonce
+     * @param string $key
+     * @return string
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function secretbox($plaintext, $nonce, $key)
+    {
+        /** @var string $subkey */
+        $subkey = ParagonIE_Sodium_Core32_HSalsa20::hsalsa20($nonce, $key);
+
+        /** @var string $block0 */
+        $block0 = str_repeat("\x00", 32);
+
+        /** @var int $mlen - Length of the plaintext message */
+        $mlen = ParagonIE_Sodium_Core32_Util::strlen($plaintext);
+        $mlen0 = $mlen;
+        if ($mlen0 > 64 - self::secretbox_xsalsa20poly1305_ZEROBYTES) {
+            $mlen0 = 64 - self::secretbox_xsalsa20poly1305_ZEROBYTES;
+        }
+        $block0 .= ParagonIE_Sodium_Core32_Util::substr($plaintext, 0, $mlen0);
+
+        /** @var string $block0 */
+        $block0 = ParagonIE_Sodium_Core32_Salsa20::salsa20_xor(
+            $block0,
+            ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8),
+            $subkey
+        );
+
+        /** @var string $c */
+        $c = ParagonIE_Sodium_Core32_Util::substr(
+            $block0,
+            self::secretbox_xsalsa20poly1305_ZEROBYTES
+        );
+        if ($mlen > $mlen0) {
+            $c .= ParagonIE_Sodium_Core32_Salsa20::salsa20_xor_ic(
+                ParagonIE_Sodium_Core32_Util::substr(
+                    $plaintext,
+                    self::secretbox_xsalsa20poly1305_ZEROBYTES
+                ),
+                ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8),
+                1,
+                $subkey
+            );
+        }
+        $state = new ParagonIE_Sodium_Core32_Poly1305_State(
+            ParagonIE_Sodium_Core32_Util::substr(
+                $block0,
+                0,
+                self::onetimeauth_poly1305_KEYBYTES
+            )
+        );
+        try {
+            ParagonIE_Sodium_Compat::memzero($block0);
+            ParagonIE_Sodium_Compat::memzero($subkey);
+        } catch (SodiumException $ex) {
+            $block0 = null;
+            $subkey = null;
+        }
+
+        $state->update($c);
+
+        /** @var string $c - MAC || ciphertext */
+        $c = $state->finish() . $c;
+        unset($state);
+
+        return $c;
+    }
+
+    /**
+     * Decrypt a ciphertext generated via secretbox().
+     *
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $ciphertext
+     * @param string $nonce
+     * @param string $key
+     * @return string
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function secretbox_open($ciphertext, $nonce, $key)
+    {
+        /** @var string $mac */
+        $mac = ParagonIE_Sodium_Core32_Util::substr(
+            $ciphertext,
+            0,
+            self::secretbox_xsalsa20poly1305_MACBYTES
+        );
+
+        /** @var string $c */
+        $c = ParagonIE_Sodium_Core32_Util::substr(
+            $ciphertext,
+            self::secretbox_xsalsa20poly1305_MACBYTES
+        );
+
+        /** @var int $clen */
+        $clen = ParagonIE_Sodium_Core32_Util::strlen($c);
+
+        /** @var string $subkey */
+        $subkey = ParagonIE_Sodium_Core32_HSalsa20::hsalsa20($nonce, $key);
+
+        /** @var string $block0 */
+        $block0 = ParagonIE_Sodium_Core32_Salsa20::salsa20(
+            64,
+            ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8),
+            $subkey
+        );
+        $verified = ParagonIE_Sodium_Core32_Poly1305::onetimeauth_verify(
+            $mac,
+            $c,
+            ParagonIE_Sodium_Core32_Util::substr($block0, 0, 32)
+        );
+        if (!$verified) {
+            try {
+                ParagonIE_Sodium_Compat::memzero($subkey);
+            } catch (SodiumException $ex) {
+                $subkey = null;
+            }
+            throw new SodiumException('Invalid MAC');
+        }
+
+        /** @var string $m - Decrypted message */
+        $m = ParagonIE_Sodium_Core32_Util::xorStrings(
+            ParagonIE_Sodium_Core32_Util::substr($block0, self::secretbox_xsalsa20poly1305_ZEROBYTES),
+            ParagonIE_Sodium_Core32_Util::substr($c, 0, self::secretbox_xsalsa20poly1305_ZEROBYTES)
+        );
+        if ($clen > self::secretbox_xsalsa20poly1305_ZEROBYTES) {
+            // We had more than 1 block, so let's continue to decrypt the rest.
+            $m .= ParagonIE_Sodium_Core32_Salsa20::salsa20_xor_ic(
+                ParagonIE_Sodium_Core32_Util::substr(
+                    $c,
+                    self::secretbox_xsalsa20poly1305_ZEROBYTES
+                ),
+                ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8),
+                1,
+                (string) $subkey
+            );
+        }
+        return $m;
+    }
+
+    /**
+     * XChaCha20-Poly1305 authenticated symmetric-key encryption.
+     *
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $plaintext
+     * @param string $nonce
+     * @param string $key
+     * @return string
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function secretbox_xchacha20poly1305($plaintext, $nonce, $key)
+    {
+        /** @var string $subkey */
+        $subkey = ParagonIE_Sodium_Core32_HChaCha20::hChaCha20(
+            ParagonIE_Sodium_Core32_Util::substr($nonce, 0, 16),
+            $key
+        );
+        $nonceLast = ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8);
+
+        /** @var string $block0 */
+        $block0 = str_repeat("\x00", 32);
+
+        /** @var int $mlen - Length of the plaintext message */
+        $mlen = ParagonIE_Sodium_Core32_Util::strlen($plaintext);
+        $mlen0 = $mlen;
+        if ($mlen0 > 64 - self::secretbox_xchacha20poly1305_ZEROBYTES) {
+            $mlen0 = 64 - self::secretbox_xchacha20poly1305_ZEROBYTES;
+        }
+        $block0 .= ParagonIE_Sodium_Core32_Util::substr($plaintext, 0, $mlen0);
+
+        /** @var string $block0 */
+        $block0 = ParagonIE_Sodium_Core32_ChaCha20::streamXorIc(
+            $block0,
+            $nonceLast,
+            $subkey
+        );
+
+        /** @var string $c */
+        $c = ParagonIE_Sodium_Core32_Util::substr(
+            $block0,
+            self::secretbox_xchacha20poly1305_ZEROBYTES
+        );
+        if ($mlen > $mlen0) {
+            $c .= ParagonIE_Sodium_Core32_ChaCha20::streamXorIc(
+                ParagonIE_Sodium_Core32_Util::substr(
+                    $plaintext,
+                    self::secretbox_xchacha20poly1305_ZEROBYTES
+                ),
+                $nonceLast,
+                $subkey,
+                ParagonIE_Sodium_Core32_Util::store64_le(1)
+            );
+        }
+        $state = new ParagonIE_Sodium_Core32_Poly1305_State(
+            ParagonIE_Sodium_Core32_Util::substr(
+                $block0,
+                0,
+                self::onetimeauth_poly1305_KEYBYTES
+            )
+        );
+        try {
+            ParagonIE_Sodium_Compat::memzero($block0);
+            ParagonIE_Sodium_Compat::memzero($subkey);
+        } catch (SodiumException $ex) {
+            $block0 = null;
+            $subkey = null;
+        }
+
+        $state->update($c);
+
+        /** @var string $c - MAC || ciphertext */
+        $c = $state->finish() . $c;
+        unset($state);
+
+        return $c;
+    }
+
+    /**
+     * Decrypt a ciphertext generated via secretbox_xchacha20poly1305().
+     *
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $ciphertext
+     * @param string $nonce
+     * @param string $key
+     * @return string
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function secretbox_xchacha20poly1305_open($ciphertext, $nonce, $key)
+    {
+        /** @var string $mac */
+        $mac = ParagonIE_Sodium_Core32_Util::substr(
+            $ciphertext,
+            0,
+            self::secretbox_xchacha20poly1305_MACBYTES
+        );
+
+        /** @var string $c */
+        $c = ParagonIE_Sodium_Core32_Util::substr(
+            $ciphertext,
+            self::secretbox_xchacha20poly1305_MACBYTES
+        );
+
+        /** @var int $clen */
+        $clen = ParagonIE_Sodium_Core32_Util::strlen($c);
+
+        /** @var string $subkey */
+        $subkey = ParagonIE_Sodium_Core32_HChaCha20::hchacha20($nonce, $key);
+
+        /** @var string $block0 */
+        $block0 = ParagonIE_Sodium_Core32_ChaCha20::stream(
+            64,
+            ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8),
+            $subkey
+        );
+        $verified = ParagonIE_Sodium_Core32_Poly1305::onetimeauth_verify(
+            $mac,
+            $c,
+            ParagonIE_Sodium_Core32_Util::substr($block0, 0, 32)
+        );
+
+        if (!$verified) {
+            try {
+                ParagonIE_Sodium_Compat::memzero($subkey);
+            } catch (SodiumException $ex) {
+                $subkey = null;
+            }
+            throw new SodiumException('Invalid MAC');
+        }
+
+        /** @var string $m - Decrypted message */
+        $m = ParagonIE_Sodium_Core32_Util::xorStrings(
+            ParagonIE_Sodium_Core32_Util::substr($block0, self::secretbox_xchacha20poly1305_ZEROBYTES),
+            ParagonIE_Sodium_Core32_Util::substr($c, 0, self::secretbox_xchacha20poly1305_ZEROBYTES)
+        );
+
+        if ($clen > self::secretbox_xchacha20poly1305_ZEROBYTES) {
+            // We had more than 1 block, so let's continue to decrypt the rest.
+            $m .= ParagonIE_Sodium_Core32_ChaCha20::streamXorIc(
+                ParagonIE_Sodium_Core32_Util::substr(
+                    $c,
+                    self::secretbox_xchacha20poly1305_ZEROBYTES
+                ),
+                ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8),
+                (string) $subkey,
+                ParagonIE_Sodium_Core32_Util::store64_le(1)
+            );
+        }
+        return $m;
+    }
+
+    /**
+     * Detached Ed25519 signature.
+     *
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $message
+     * @param string $sk
+     * @return string
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function sign_detached($message, $sk)
+    {
+        return ParagonIE_Sodium_Core32_Ed25519::sign_detached($message, $sk);
+    }
+
+    /**
+     * Attached Ed25519 signature. (Returns a signed message.)
+     *
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $message
+     * @param string $sk
+     * @return string
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function sign($message, $sk)
+    {
+        return ParagonIE_Sodium_Core32_Ed25519::sign($message, $sk);
+    }
+
+    /**
+     * Opens a signed message. If valid, returns the message.
+     *
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $signedMessage
+     * @param string $pk
+     * @return string
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function sign_open($signedMessage, $pk)
+    {
+        return ParagonIE_Sodium_Core32_Ed25519::sign_open($signedMessage, $pk);
+    }
+
+    /**
+     * Verify a detached signature of a given message and public key.
+     *
+     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
+     *
+     * @param string $signature
+     * @param string $message
+     * @param string $pk
+     * @return bool
+     * @throws SodiumException
+     * @throws TypeError
+     */
+    public static function sign_verify_detached($signature, $message, $pk)
+    {
+        return ParagonIE_Sodium_Core32_Ed25519::verify_detached($signature, $message, $pk);
+    }
+}